├── .gitignore ├── LICENSE ├── README.md ├── gazebo_worlds ├── CMakeLists.txt ├── launch │ ├── rviz.launch │ ├── world0.launch │ ├── world1.launch │ ├── world2.launch │ └── world3.launch ├── package.xml └── urdf │ ├── herbie.gazebo │ ├── herbie.xacro │ └── macros.xacro ├── mpc ├── CMakeLists.txt ├── launch │ └── start_controller.launch ├── package.xml └── src │ ├── MPC.py │ └── controller.py ├── mpc_orca ├── CMakeLists.txt ├── launch │ ├── start_controllers_world1.launch │ ├── start_controllers_world2.launch │ └── start_controllers_world3.launch ├── package.xml └── src │ ├── MPC_ORCA.py │ ├── controller.py │ ├── pyorca.py │ └── test_controller.py ├── orca ├── CMakeLists.txt ├── package.xml └── src │ ├── controller.py │ ├── dense_diff_controller.py │ ├── dense_omni_controller.py │ ├── diff_omni_controller.py │ ├── halfplaneintersect.py │ ├── omni_controller.py │ ├── pyorca.py │ └── test.py ├── rvo ├── CMakeLists.txt ├── package.xml └── src │ ├── RVO.py │ ├── controller.py │ ├── dense_diff_controller.py │ ├── dense_omni_controller.py │ ├── diff_omni_controller.py │ └── omni_controller.py └── stage_worlds ├── circle.png ├── dense_diff.world ├── dense_omni.world ├── world1.world ├── world2.world └── world_test.world /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Glauber Rodrigues Leite 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Local Collision Avoidance Techniques on Multi-Agent Robot Navigation Systems 2 | 3 | This repository covers a set of algorithms that provides local control strategies for autonomous robots navigating on a multi-agent scenario, where each robot tries to reach its goal as fast as it can, while mantaining a collision-free trajectory, based only on other agents velocity readings without any direct communication between them. 4 | 5 | ## About the code 6 | 7 | The algorithms were created to work over the [ROS](https://www.ros.org/) environment, specifically the *Melodic* distribution running over Ubuntu 18.04, but they may work with other distributions. 8 | 9 | ## Dependencies 10 | 11 | * [ROS Melodic](https://www.ros.org/) 12 | * [Python 2.7](https://www.python.org/) 13 | * [Gazebo](http://gazebosim.org/) * 14 | * [Stage](http://wiki.ros.org/stage) * 15 | * [OSQP (Operator Splitting Quadratic Program)](https://osqp.org/) 16 | 17 | \* Generally comes with a full ROS install 18 | 19 | ## Simulation scenarios 20 | 21 | Firstly, the ROS MASTER needed to be started: 22 | 23 | `$ roscore` 24 | 25 | ### ROS Stage 26 | 27 | There are worlds created for Stage simulation inside the *stage_worlds* folder. To run a simulation, e.g. *world1.world*: 28 | 29 | `$ rosrun stage_ros stageros /stage_worlds/world1.world` 30 | 31 | ### Gazebo 32 | 33 | For this simulation, a [Yujin Robot Kobuki](http://kobuki.yujinrobot.com/) was used, so it needs the driver URDF description and components for that robot. If it is not found within the default ROS install, it can be downloaded and built inside the catkin workspace using this [repository](https://github.com/yujinrobot/kobuki) (select the right ROS distribution branch). 34 | 35 | With all set, the gazebo environment with the robots can be started using the launch file inside *mpc_orca* folder: 36 | 37 | `$ roslaunch mpc_orca kobuki_duo.launch` -------------------------------------------------------------------------------- /gazebo_worlds/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.3) 2 | project(gazebo_worlds) 3 | 4 | ## Compile as C++11, supported in ROS Kinetic and newer 5 | # add_compile_options(-std=c++11) 6 | 7 | ## Find catkin macros and libraries 8 | ## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) 9 | ## is used, also find other catkin packages 10 | find_package(catkin REQUIRED) 11 | 12 | ## System dependencies are found with CMake's conventions 13 | # find_package(Boost REQUIRED COMPONENTS system) 14 | 15 | 16 | ## Uncomment this if the package has a setup.py. This macro ensures 17 | ## modules and global scripts declared therein get installed 18 | ## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html 19 | # catkin_python_setup() 20 | 21 | ################################################ 22 | ## Declare ROS messages, services and actions ## 23 | ################################################ 24 | 25 | ## To declare and build messages, services or actions from within this 26 | ## package, follow these steps: 27 | ## * Let MSG_DEP_SET be the set of packages whose message types you use in 28 | ## your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...). 29 | ## * In the file package.xml: 30 | ## * add a build_depend tag for "message_generation" 31 | ## * add a build_depend and a exec_depend tag for each package in MSG_DEP_SET 32 | ## * If MSG_DEP_SET isn't empty the following dependency has been pulled in 33 | ## but can be declared for certainty nonetheless: 34 | ## * add a exec_depend tag for "message_runtime" 35 | ## * In this file (CMakeLists.txt): 36 | ## * add "message_generation" and every package in MSG_DEP_SET to 37 | ## find_package(catkin REQUIRED COMPONENTS ...) 38 | ## * add "message_runtime" and every package in MSG_DEP_SET to 39 | ## catkin_package(CATKIN_DEPENDS ...) 40 | ## * uncomment the add_*_files sections below as needed 41 | ## and list every .msg/.srv/.action file to be processed 42 | ## * uncomment the generate_messages entry below 43 | ## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...) 44 | 45 | ## Generate messages in the 'msg' folder 46 | # add_message_files( 47 | # FILES 48 | # Message1.msg 49 | # Message2.msg 50 | # ) 51 | 52 | ## Generate services in the 'srv' folder 53 | # add_service_files( 54 | # FILES 55 | # Service1.srv 56 | # Service2.srv 57 | # ) 58 | 59 | ## Generate actions in the 'action' folder 60 | # add_action_files( 61 | # FILES 62 | # Action1.action 63 | # Action2.action 64 | # ) 65 | 66 | ## Generate added messages and services with any dependencies listed here 67 | # generate_messages( 68 | # DEPENDENCIES 69 | # std_msgs # Or other packages containing msgs 70 | # ) 71 | 72 | ################################################ 73 | ## Declare ROS dynamic reconfigure parameters ## 74 | ################################################ 75 | 76 | ## To declare and build dynamic reconfigure parameters within this 77 | ## package, follow these steps: 78 | ## * In the file package.xml: 79 | ## * add a build_depend and a exec_depend tag for "dynamic_reconfigure" 80 | ## * In this file (CMakeLists.txt): 81 | ## * add "dynamic_reconfigure" to 82 | ## find_package(catkin REQUIRED COMPONENTS ...) 83 | ## * uncomment the "generate_dynamic_reconfigure_options" section below 84 | ## and list every .cfg file to be processed 85 | 86 | ## Generate dynamic reconfigure parameters in the 'cfg' folder 87 | # generate_dynamic_reconfigure_options( 88 | # cfg/DynReconf1.cfg 89 | # cfg/DynReconf2.cfg 90 | # ) 91 | 92 | ################################### 93 | ## catkin specific configuration ## 94 | ################################### 95 | ## The catkin_package macro generates cmake config files for your package 96 | ## Declare things to be passed to dependent projects 97 | ## INCLUDE_DIRS: uncomment this if your package contains header files 98 | ## LIBRARIES: libraries you create in this project that dependent projects also need 99 | ## CATKIN_DEPENDS: catkin_packages dependent projects also need 100 | ## DEPENDS: system dependencies of this project that dependent projects also need 101 | catkin_package( 102 | # INCLUDE_DIRS include 103 | # LIBRARIES gazebo_worlds 104 | # CATKIN_DEPENDS other_catkin_pkg 105 | # DEPENDS system_lib 106 | ) 107 | 108 | ########### 109 | ## Build ## 110 | ########### 111 | 112 | ## Specify additional locations of header files 113 | ## Your package locations should be listed before other locations 114 | include_directories( 115 | # include 116 | # ${catkin_INCLUDE_DIRS} 117 | ) 118 | 119 | ## Declare a C++ library 120 | # add_library(${PROJECT_NAME} 121 | # src/${PROJECT_NAME}/gazebo_worlds.cpp 122 | # ) 123 | 124 | ## Add cmake target dependencies of the library 125 | ## as an example, code may need to be generated before libraries 126 | ## either from message generation or dynamic reconfigure 127 | # add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) 128 | 129 | ## Declare a C++ executable 130 | ## With catkin_make all packages are built within a single CMake context 131 | ## The recommended prefix ensures that target names across packages don't collide 132 | # add_executable(${PROJECT_NAME}_node src/gazebo_worlds_node.cpp) 133 | 134 | ## Rename C++ executable without prefix 135 | ## The above recommended prefix causes long target names, the following renames the 136 | ## target back to the shorter version for ease of user use 137 | ## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node" 138 | # set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "") 139 | 140 | ## Add cmake target dependencies of the executable 141 | ## same as for the library above 142 | # add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) 143 | 144 | ## Specify libraries to link a library or executable target against 145 | # target_link_libraries(${PROJECT_NAME}_node 146 | # ${catkin_LIBRARIES} 147 | # ) 148 | 149 | ############# 150 | ## Install ## 151 | ############# 152 | 153 | # all install targets should use catkin DESTINATION variables 154 | # See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html 155 | 156 | ## Mark executable scripts (Python etc.) for installation 157 | ## in contrast to setup.py, you can choose the destination 158 | # install(PROGRAMS 159 | # scripts/my_python_script 160 | # DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} 161 | # ) 162 | 163 | ## Mark executables for installation 164 | ## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_executables.html 165 | # install(TARGETS ${PROJECT_NAME}_node 166 | # RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} 167 | # ) 168 | 169 | ## Mark libraries for installation 170 | ## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_libraries.html 171 | # install(TARGETS ${PROJECT_NAME} 172 | # ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} 173 | # LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} 174 | # RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION} 175 | # ) 176 | 177 | ## Mark cpp header files for installation 178 | # install(DIRECTORY include/${PROJECT_NAME}/ 179 | # DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} 180 | # FILES_MATCHING PATTERN "*.h" 181 | # PATTERN ".svn" EXCLUDE 182 | # ) 183 | 184 | ## Mark other files for installation (e.g. launch and bag files, etc.) 185 | # install(FILES 186 | # # myfile1 187 | # # myfile2 188 | # DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} 189 | # ) 190 | 191 | ############# 192 | ## Testing ## 193 | ############# 194 | 195 | ## Add gtest based cpp test target and link libraries 196 | # catkin_add_gtest(${PROJECT_NAME}-test test/test_gazebo_worlds.cpp) 197 | # if(TARGET ${PROJECT_NAME}-test) 198 | # target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME}) 199 | # endif() 200 | 201 | ## Add folders to be run by python nosetests 202 | # catkin_add_nosetests(test) 203 | -------------------------------------------------------------------------------- /gazebo_worlds/launch/rviz.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /gazebo_worlds/launch/world0.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /gazebo_worlds/launch/world1.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /gazebo_worlds/launch/world2.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /gazebo_worlds/launch/world3.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /gazebo_worlds/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | gazebo_worlds 4 | 0.0.0 5 | The gazebo_worlds package 6 | 7 | 8 | 9 | 10 | glauber 11 | 12 | 13 | 14 | 15 | 16 | TODO 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | catkin 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /gazebo_worlds/urdf/herbie.gazebo: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 0 6 | 0 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | true 28 | 100 29 | joint_left_wheel 30 | joint_right_wheel 31 | 0.108 32 | 0.0313 33 | 1 34 | 100 35 | cmd_vel 36 | odom 37 | odom 38 | link_chassis 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /gazebo_worlds/urdf/herbie.xacro: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 0 0 0 0 0 0 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 0 45 | 0 46 | 1.0 47 | 1.0 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /gazebo_worlds/urdf/macros.xacro: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /mpc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.3) 2 | project(mpc) 3 | 4 | ## Compile as C++11, supported in ROS Kinetic and newer 5 | # add_compile_options(-std=c++11) 6 | 7 | ## Find catkin macros and libraries 8 | ## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) 9 | ## is used, also find other catkin packages 10 | find_package(catkin REQUIRED) 11 | 12 | ## System dependencies are found with CMake's conventions 13 | # find_package(Boost REQUIRED COMPONENTS system) 14 | 15 | 16 | ## Uncomment this if the package has a setup.py. This macro ensures 17 | ## modules and global scripts declared therein get installed 18 | ## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html 19 | # catkin_python_setup() 20 | 21 | ################################################ 22 | ## Declare ROS messages, services and actions ## 23 | ################################################ 24 | 25 | ## To declare and build messages, services or actions from within this 26 | ## package, follow these steps: 27 | ## * Let MSG_DEP_SET be the set of packages whose message types you use in 28 | ## your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...). 29 | ## * In the file package.xml: 30 | ## * add a build_depend tag for "message_generation" 31 | ## * add a build_depend and a exec_depend tag for each package in MSG_DEP_SET 32 | ## * If MSG_DEP_SET isn't empty the following dependency has been pulled in 33 | ## but can be declared for certainty nonetheless: 34 | ## * add a exec_depend tag for "message_runtime" 35 | ## * In this file (CMakeLists.txt): 36 | ## * add "message_generation" and every package in MSG_DEP_SET to 37 | ## find_package(catkin REQUIRED COMPONENTS ...) 38 | ## * add "message_runtime" and every package in MSG_DEP_SET to 39 | ## catkin_package(CATKIN_DEPENDS ...) 40 | ## * uncomment the add_*_files sections below as needed 41 | ## and list every .msg/.srv/.action file to be processed 42 | ## * uncomment the generate_messages entry below 43 | ## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...) 44 | 45 | ## Generate messages in the 'msg' folder 46 | # add_message_files( 47 | # FILES 48 | # Message1.msg 49 | # Message2.msg 50 | # ) 51 | 52 | ## Generate services in the 'srv' folder 53 | # add_service_files( 54 | # FILES 55 | # Service1.srv 56 | # Service2.srv 57 | # ) 58 | 59 | ## Generate actions in the 'action' folder 60 | # add_action_files( 61 | # FILES 62 | # Action1.action 63 | # Action2.action 64 | # ) 65 | 66 | ## Generate added messages and services with any dependencies listed here 67 | # generate_messages( 68 | # DEPENDENCIES 69 | # std_msgs # Or other packages containing msgs 70 | # ) 71 | 72 | ################################################ 73 | ## Declare ROS dynamic reconfigure parameters ## 74 | ################################################ 75 | 76 | ## To declare and build dynamic reconfigure parameters within this 77 | ## package, follow these steps: 78 | ## * In the file package.xml: 79 | ## * add a build_depend and a exec_depend tag for "dynamic_reconfigure" 80 | ## * In this file (CMakeLists.txt): 81 | ## * add "dynamic_reconfigure" to 82 | ## find_package(catkin REQUIRED COMPONENTS ...) 83 | ## * uncomment the "generate_dynamic_reconfigure_options" section below 84 | ## and list every .cfg file to be processed 85 | 86 | ## Generate dynamic reconfigure parameters in the 'cfg' folder 87 | # generate_dynamic_reconfigure_options( 88 | # cfg/DynReconf1.cfg 89 | # cfg/DynReconf2.cfg 90 | # ) 91 | 92 | ################################### 93 | ## catkin specific configuration ## 94 | ################################### 95 | ## The catkin_package macro generates cmake config files for your package 96 | ## Declare things to be passed to dependent projects 97 | ## INCLUDE_DIRS: uncomment this if your package contains header files 98 | ## LIBRARIES: libraries you create in this project that dependent projects also need 99 | ## CATKIN_DEPENDS: catkin_packages dependent projects also need 100 | ## DEPENDS: system dependencies of this project that dependent projects also need 101 | catkin_package( 102 | # INCLUDE_DIRS include 103 | # LIBRARIES mpc 104 | # CATKIN_DEPENDS other_catkin_pkg 105 | # DEPENDS system_lib 106 | ) 107 | 108 | ########### 109 | ## Build ## 110 | ########### 111 | 112 | ## Specify additional locations of header files 113 | ## Your package locations should be listed before other locations 114 | include_directories( 115 | # include 116 | # ${catkin_INCLUDE_DIRS} 117 | ) 118 | 119 | ## Declare a C++ library 120 | # add_library(${PROJECT_NAME} 121 | # src/${PROJECT_NAME}/mpc.cpp 122 | # ) 123 | 124 | ## Add cmake target dependencies of the library 125 | ## as an example, code may need to be generated before libraries 126 | ## either from message generation or dynamic reconfigure 127 | # add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) 128 | 129 | ## Declare a C++ executable 130 | ## With catkin_make all packages are built within a single CMake context 131 | ## The recommended prefix ensures that target names across packages don't collide 132 | # add_executable(${PROJECT_NAME}_node src/mpc_node.cpp) 133 | 134 | ## Rename C++ executable without prefix 135 | ## The above recommended prefix causes long target names, the following renames the 136 | ## target back to the shorter version for ease of user use 137 | ## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node" 138 | # set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "") 139 | 140 | ## Add cmake target dependencies of the executable 141 | ## same as for the library above 142 | # add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) 143 | 144 | ## Specify libraries to link a library or executable target against 145 | # target_link_libraries(${PROJECT_NAME}_node 146 | # ${catkin_LIBRARIES} 147 | # ) 148 | 149 | ############# 150 | ## Install ## 151 | ############# 152 | 153 | # all install targets should use catkin DESTINATION variables 154 | # See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html 155 | 156 | ## Mark executable scripts (Python etc.) for installation 157 | ## in contrast to setup.py, you can choose the destination 158 | # install(PROGRAMS 159 | # scripts/my_python_script 160 | # DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} 161 | # ) 162 | 163 | ## Mark executables for installation 164 | ## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_executables.html 165 | # install(TARGETS ${PROJECT_NAME}_node 166 | # RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} 167 | # ) 168 | 169 | ## Mark libraries for installation 170 | ## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_libraries.html 171 | # install(TARGETS ${PROJECT_NAME} 172 | # ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} 173 | # LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} 174 | # RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION} 175 | # ) 176 | 177 | ## Mark cpp header files for installation 178 | # install(DIRECTORY include/${PROJECT_NAME}/ 179 | # DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} 180 | # FILES_MATCHING PATTERN "*.h" 181 | # PATTERN ".svn" EXCLUDE 182 | # ) 183 | 184 | ## Mark other files for installation (e.g. launch and bag files, etc.) 185 | # install(FILES 186 | # # myfile1 187 | # # myfile2 188 | # DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} 189 | # ) 190 | 191 | ############# 192 | ## Testing ## 193 | ############# 194 | 195 | ## Add gtest based cpp test target and link libraries 196 | # catkin_add_gtest(${PROJECT_NAME}-test test/test_mpc.cpp) 197 | # if(TARGET ${PROJECT_NAME}-test) 198 | # target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME}) 199 | # endif() 200 | 201 | ## Add folders to be run by python nosetests 202 | # catkin_add_nosetests(test) 203 | -------------------------------------------------------------------------------- /mpc/launch/start_controller.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | 15 | 16 | -------------------------------------------------------------------------------- /mpc/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | mpc 4 | 0.0.0 5 | The mpc package 6 | 7 | 8 | 9 | 10 | glauber 11 | 12 | 13 | 14 | 15 | 16 | TODO 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | catkin 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /mpc/src/MPC.py: -------------------------------------------------------------------------------- 1 | import osqp 2 | import numpy 3 | import scipy.sparse as sparse 4 | 5 | class MPC: 6 | 7 | def __init__(self, position, v_min, v_max, N, N_c, Ts): 8 | """ MPC-ORCA controller instance 9 | 10 | :param goal: Goal position 11 | :type goal: Numpy Array 2x1 12 | :param position: Initial position 13 | :type position: Numpy Array 2x1 14 | :param v_min: Lower velocity constraint 15 | :type v_min: float 16 | :param v_max: Upper velocity constraint 17 | :type v_max: float 18 | :param N: Prediction Horizon 19 | :type N: int 20 | :param N_c: Control Horizon 21 | :type N_c: int 22 | :param Ts: Sampling Time 23 | :type Ts: float 24 | :returns: Controller instance 25 | :type: MPCORCA 26 | """ 27 | 28 | self.N = N 29 | self.N_c = N_c 30 | self.Ts = Ts 31 | 32 | # Linear Dynamics 33 | # x = [p_x, p_y, v_x, v_y]' 34 | # u = [a_x, a_y]' 35 | Ad = sparse.csc_matrix([ 36 | [1., 0., Ts, 0. ], 37 | [0., 1., 0., Ts ], 38 | [0., 0., 1., 0. ], 39 | [0., 0., 0., 1. ] 40 | ]) 41 | 42 | Bd = sparse.csc_matrix([ 43 | [0.5 * Ts ** 2, 0. ], 44 | [0., 0.5 * Ts ** 2 ], 45 | [Ts, 0. ], 46 | [0., Ts ] 47 | ]) 48 | [self.nx, self.nu] = Bd.shape 49 | 50 | # State constraints 51 | xmin = numpy.array([-numpy.inf, -numpy.inf, v_min, v_min]) 52 | xmax = numpy.array([numpy.inf, numpy.inf, v_max, v_max]) 53 | umin = numpy.array([v_min/Ts, v_min/Ts]) 54 | umax = numpy.array([v_max/Ts, v_max/Ts]) 55 | 56 | # Initial state 57 | self.x_0 = numpy.array([position[0], position[1], 0., 0.]) 58 | 59 | # Setpoint 60 | x_r = self.x_0 61 | 62 | # MPC objective function 63 | Q_0 = sparse.diags([3.0, 3.0, 0.0, 0.0]) 64 | Q = sparse.diags([1.0, 1.0, 0.0, 0.0]) 65 | R = 0.55 * sparse.eye(self.nu) 66 | 67 | # Casting QP format 68 | # QP objective 69 | P = sparse.block_diag([Q, Q_0, sparse.kron(sparse.eye(N-1), Q), sparse.kron(sparse.eye(N), R)]).tocsc() 70 | self.q = numpy.hstack([-Q.dot(x_r), -Q_0.dot(x_r), numpy.kron(numpy.ones(N-1), -Q.dot(x_r)), numpy.zeros(N * self.nu)]) 71 | 72 | # QP constraints 73 | # - linear dynamics 74 | Ax = sparse.kron(sparse.eye(N+1),-sparse.eye(self.nx)) + sparse.kron(sparse.eye(N+1, k=-1), Ad) 75 | Bu = sparse.kron(sparse.vstack([sparse.csc_matrix((1, N)), sparse.eye(N)]), Bd) 76 | A_eq = sparse.hstack([Ax, Bu]) 77 | l_eq = numpy.hstack([-self.x_0, numpy.zeros(N*self.nx)]) 78 | u_eq = l_eq 79 | 80 | # - Control horizon constraint 81 | A_N_c = sparse.hstack([numpy.zeros((self.nu * (N - N_c), (N+1) * self.nx)), \ 82 | numpy.zeros((self.nu * (N - N_c), (N_c - 1) * self.nu)), \ 83 | -sparse.kron(numpy.ones(((N - N_c), 1)), sparse.eye(self.nu)), \ 84 | sparse.eye(self.nu * (N - N_c))]) 85 | l_N_c = numpy.zeros(self.nu * (N - N_c)) 86 | u_N_c = numpy.zeros(self.nu * (N - N_c)) 87 | 88 | # - input and state constraints 89 | A_ineq = sparse.eye((N+1) * self.nx + N * self.nu) 90 | l_ineq = numpy.hstack([numpy.kron(numpy.ones(N+1), xmin), numpy.kron(numpy.ones(N), umin)]) 91 | u_ineq = numpy.hstack([numpy.kron(numpy.ones(N+1), xmax), numpy.kron(numpy.ones(N), umax)]) 92 | 93 | # OSQP constraints 94 | A = sparse.vstack([A_eq, A_N_c, A_ineq]).tocsc() 95 | self.l = numpy.hstack([l_eq, l_N_c, l_ineq]) 96 | self.u = numpy.hstack([u_eq, u_N_c, u_ineq]) 97 | self.Q_0 = Q_0 98 | self.Q = Q 99 | self.R = R 100 | 101 | # Setting problem 102 | self.problem = osqp.OSQP() 103 | self.problem.setup(P, self.q, A, self.l, self.u, warm_start=True, verbose=False) 104 | 105 | def getNewVelocity(self, setpoint): 106 | # Updating initial conditions 107 | self.q = numpy.hstack([-self.Q.dot(setpoint[0:self.nx]), -self.Q_0.dot(setpoint[self.nx:2*self.nx]), numpy.dot(sparse.kron(sparse.eye(self.N-1), -self.Q).toarray(), setpoint[2*self.nx:]), numpy.zeros(self.N * self.nu)]) 108 | 109 | self.l[:self.nx] = -self.x_0 110 | self.u[:self.nx] = -self.x_0 111 | 112 | self.problem.update(q=self.q, l=self.l, u=self.u) 113 | result = self.problem.solve() 114 | 115 | if result.info.status == 'solved': 116 | # return the first resulting velocity after control action 117 | #print(result.x[-self.N*self.nu:]) 118 | return [result.x[(self.nx + 2):(self.nx + 4)], result.x[-self.N*self.nu:-(self.N-1)*self.nu]] 119 | else: 120 | print('unsolved') 121 | return [numpy.array([self.x_0[2], self.x_0[3]]), numpy.zeros(2)] 122 | #damping = 0.05 123 | #return numpy.array([self.agent.velocity[0] - numpy.sign(self.agent.velocity[0])*damping, self.agent.velocity[1] - numpy.sign(self.agent.velocity[1])*damping]) 124 | 125 | -------------------------------------------------------------------------------- /mpc/src/controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import sys 4 | import rospy 5 | import numpy as np 6 | from geometry_msgs.msg import Twist, Vector3 7 | from gazebo_msgs.msg import ModelStates 8 | from MPC import MPC 9 | 10 | N = 10 11 | N_c = 5 12 | Ts = 0.1 13 | X = np.array([0., 0.]) 14 | orientation = 0 15 | V = np.array([0., 0.]) 16 | V_min = -5 17 | V_max = 5 18 | 19 | goal = np.array([float(sys.argv[1]), float(sys.argv[2])]) 20 | 21 | def accelerationTransform(a, v, w, theta_0): 22 | """This function applies the linearization transformation on acceleration values 23 | based on equation 2.11 24 | """ 25 | d = 0.2 26 | cos_theta = np.cos(theta_0) 27 | sin_theta = np.sin(theta_0) 28 | inverse = np.linalg.inv(np.array([[cos_theta, -d * sin_theta],[sin_theta, d * cos_theta]])) 29 | term1 = a[0] + v * w * sin_theta + d * (w**2) * cos_theta 30 | term2 = a[1] - v * w * cos_theta + d * (w**2) * sin_theta 31 | acc = np.matmul(inverse, np.vstack([term1, term2])) 32 | acc = acc.T 33 | 34 | return acc[0] 35 | 36 | def updateWorld(msg): 37 | """This funcion is called whenever the gazebo/model_states publishes. This function 38 | updates the world variables as fast as possible""" 39 | global X, V, orientation 40 | X = np.array([float(msg.pose[1].position.x), float(msg.pose[1].position.y)]) 41 | V = np.array([float(msg.twist[1].linear.x), float(msg.twist[1].linear.y)]) 42 | orientation = np.arctan2(2 * float(msg.pose[1].orientation.w) * float(msg.pose[1].orientation.z), \ 43 | 1 - 2 * float(msg.pose[1].orientation.z)**2) 44 | 45 | rospy.init_node('mpc_controller') 46 | 47 | # Subscribing on model_states instead of robot/odom, to avoid unnecessary noise 48 | rospy.Subscriber('/gazebo/model_states', ModelStates, updateWorld) 49 | 50 | # Velocity publishers 51 | pub = rospy.Publisher('/cmd_vel', Twist, queue_size=10) 52 | 53 | # Setpoint Publishers 54 | pub_setpoint_pos = rospy.Publisher('/setpoint_pos', Vector3, queue_size=10) 55 | pub_setpoint_vel = rospy.Publisher('/setpoint_vel', Vector3, queue_size=10) 56 | 57 | setpoint_pos = Vector3() 58 | setpoint_vel = Vector3() 59 | 60 | # Initializing Controllers 61 | controller = MPC(X, V_min, V_max, N, N_c, Ts) 62 | 63 | # Global path planning 64 | initial = np.copy(X) 65 | t0 = 10.0 66 | growth = 0.5 67 | logistic = lambda t: 1/(1 + np.exp(- growth * (t - t0))) 68 | d_logistic = lambda t: growth * logistic(t) * (1 - logistic(t)) 69 | P_des = lambda t: goal * logistic(t) + initial * (1 - logistic(t)) 70 | V_des = lambda t: goal * d_logistic(t) - initial * d_logistic(t) 71 | 72 | t = 0 73 | 74 | vel = Twist() 75 | 76 | while not rospy.is_shutdown(): 77 | 78 | # Updating setpoint trajectory 79 | setpoint = np.ravel([np.append(P_des(t + k * Ts), V_des(t + k * Ts)) for k in range(0, N + 1)]) 80 | 81 | # Updating initial conditions 82 | controller.x_0 = np.array([X[0], X[1], V[0], V[1]]) 83 | 84 | # Computing optimal input values 85 | [_, acceleration] = controller.getNewVelocity(setpoint) 86 | 87 | [setpoint_pos.x, setpoint_pos.y] = P_des(t) 88 | 89 | [setpoint_vel.x, setpoint_vel.y] = V_des(t) 90 | 91 | acc = accelerationTransform(acceleration, vel.linear.x, vel.angular.z, orientation) 92 | 93 | vel.linear.x = vel.linear.x + acc[0] * Ts 94 | vel.angular.z = vel.angular.z + acc[1] * Ts 95 | 96 | pub.publish(vel) 97 | 98 | pub_setpoint_pos.publish(setpoint_pos) 99 | pub_setpoint_vel.publish(setpoint_vel) 100 | rospy.sleep(Ts) 101 | 102 | t += Ts 103 | -------------------------------------------------------------------------------- /mpc_orca/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.3) 2 | project(mpc_orca) 3 | 4 | ## Compile as C++11, supported in ROS Kinetic and newer 5 | # add_compile_options(-std=c++11) 6 | 7 | ## Find catkin macros and libraries 8 | ## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) 9 | ## is used, also find other catkin packages 10 | find_package(catkin REQUIRED COMPONENTS 11 | rospy 12 | ) 13 | 14 | ## System dependencies are found with CMake's conventions 15 | # find_package(Boost REQUIRED COMPONENTS system) 16 | 17 | 18 | ## Uncomment this if the package has a setup.py. This macro ensures 19 | ## modules and global scripts declared therein get installed 20 | ## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html 21 | # catkin_python_setup() 22 | 23 | ################################################ 24 | ## Declare ROS messages, services and actions ## 25 | ################################################ 26 | 27 | ## To declare and build messages, services or actions from within this 28 | ## package, follow these steps: 29 | ## * Let MSG_DEP_SET be the set of packages whose message types you use in 30 | ## your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...). 31 | ## * In the file package.xml: 32 | ## * add a build_depend tag for "message_generation" 33 | ## * add a build_depend and a exec_depend tag for each package in MSG_DEP_SET 34 | ## * If MSG_DEP_SET isn't empty the following dependency has been pulled in 35 | ## but can be declared for certainty nonetheless: 36 | ## * add a exec_depend tag for "message_runtime" 37 | ## * In this file (CMakeLists.txt): 38 | ## * add "message_generation" and every package in MSG_DEP_SET to 39 | ## find_package(catkin REQUIRED COMPONENTS ...) 40 | ## * add "message_runtime" and every package in MSG_DEP_SET to 41 | ## catkin_package(CATKIN_DEPENDS ...) 42 | ## * uncomment the add_*_files sections below as needed 43 | ## and list every .msg/.srv/.action file to be processed 44 | ## * uncomment the generate_messages entry below 45 | ## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...) 46 | 47 | ## Generate messages in the 'msg' folder 48 | # add_message_files( 49 | # FILES 50 | # Message1.msg 51 | # Message2.msg 52 | # ) 53 | 54 | ## Generate services in the 'srv' folder 55 | # add_service_files( 56 | # FILES 57 | # Service1.srv 58 | # Service2.srv 59 | # ) 60 | 61 | ## Generate actions in the 'action' folder 62 | # add_action_files( 63 | # FILES 64 | # Action1.action 65 | # Action2.action 66 | # ) 67 | 68 | ## Generate added messages and services with any dependencies listed here 69 | # generate_messages( 70 | # DEPENDENCIES 71 | # std_msgs # Or other packages containing msgs 72 | # ) 73 | 74 | ################################################ 75 | ## Declare ROS dynamic reconfigure parameters ## 76 | ################################################ 77 | 78 | ## To declare and build dynamic reconfigure parameters within this 79 | ## package, follow these steps: 80 | ## * In the file package.xml: 81 | ## * add a build_depend and a exec_depend tag for "dynamic_reconfigure" 82 | ## * In this file (CMakeLists.txt): 83 | ## * add "dynamic_reconfigure" to 84 | ## find_package(catkin REQUIRED COMPONENTS ...) 85 | ## * uncomment the "generate_dynamic_reconfigure_options" section below 86 | ## and list every .cfg file to be processed 87 | 88 | ## Generate dynamic reconfigure parameters in the 'cfg' folder 89 | # generate_dynamic_reconfigure_options( 90 | # cfg/DynReconf1.cfg 91 | # cfg/DynReconf2.cfg 92 | # ) 93 | 94 | ################################### 95 | ## catkin specific configuration ## 96 | ################################### 97 | ## The catkin_package macro generates cmake config files for your package 98 | ## Declare things to be passed to dependent projects 99 | ## INCLUDE_DIRS: uncomment this if your package contains header files 100 | ## LIBRARIES: libraries you create in this project that dependent projects also need 101 | ## CATKIN_DEPENDS: catkin_packages dependent projects also need 102 | ## DEPENDS: system dependencies of this project that dependent projects also need 103 | catkin_package( 104 | # INCLUDE_DIRS include 105 | # LIBRARIES mpc_orca 106 | # CATKIN_DEPENDS rospy 107 | # DEPENDS system_lib 108 | ) 109 | 110 | ########### 111 | ## Build ## 112 | ########### 113 | 114 | ## Specify additional locations of header files 115 | ## Your package locations should be listed before other locations 116 | include_directories( 117 | # include 118 | ${catkin_INCLUDE_DIRS} 119 | ) 120 | 121 | ## Declare a C++ library 122 | # add_library(${PROJECT_NAME} 123 | # src/${PROJECT_NAME}/mpc_orca.cpp 124 | # ) 125 | 126 | ## Add cmake target dependencies of the library 127 | ## as an example, code may need to be generated before libraries 128 | ## either from message generation or dynamic reconfigure 129 | # add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) 130 | 131 | ## Declare a C++ executable 132 | ## With catkin_make all packages are built within a single CMake context 133 | ## The recommended prefix ensures that target names across packages don't collide 134 | # add_executable(${PROJECT_NAME}_node src/mpc_orca_node.cpp) 135 | 136 | ## Rename C++ executable without prefix 137 | ## The above recommended prefix causes long target names, the following renames the 138 | ## target back to the shorter version for ease of user use 139 | ## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node" 140 | # set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "") 141 | 142 | ## Add cmake target dependencies of the executable 143 | ## same as for the library above 144 | # add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) 145 | 146 | ## Specify libraries to link a library or executable target against 147 | # target_link_libraries(${PROJECT_NAME}_node 148 | # ${catkin_LIBRARIES} 149 | # ) 150 | 151 | ############# 152 | ## Install ## 153 | ############# 154 | 155 | # all install targets should use catkin DESTINATION variables 156 | # See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html 157 | 158 | ## Mark executable scripts (Python etc.) for installation 159 | ## in contrast to setup.py, you can choose the destination 160 | # install(PROGRAMS 161 | # scripts/my_python_script 162 | # DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} 163 | # ) 164 | 165 | ## Mark executables for installation 166 | ## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_executables.html 167 | # install(TARGETS ${PROJECT_NAME}_node 168 | # RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} 169 | # ) 170 | 171 | ## Mark libraries for installation 172 | ## See http://docs.ros.org/melodic/api/catkin/html/howto/format1/building_libraries.html 173 | # install(TARGETS ${PROJECT_NAME} 174 | # ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} 175 | # LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} 176 | # RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION} 177 | # ) 178 | 179 | ## Mark cpp header files for installation 180 | # install(DIRECTORY include/${PROJECT_NAME}/ 181 | # DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} 182 | # FILES_MATCHING PATTERN "*.h" 183 | # PATTERN ".svn" EXCLUDE 184 | # ) 185 | 186 | ## Mark other files for installation (e.g. launch and bag files, etc.) 187 | # install(FILES 188 | # # myfile1 189 | # # myfile2 190 | # DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} 191 | # ) 192 | 193 | ############# 194 | ## Testing ## 195 | ############# 196 | 197 | ## Add gtest based cpp test target and link libraries 198 | # catkin_add_gtest(${PROJECT_NAME}-test test/test_mpc_orca.cpp) 199 | # if(TARGET ${PROJECT_NAME}-test) 200 | # target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME}) 201 | # endif() 202 | 203 | ## Add folders to be run by python nosetests 204 | # catkin_add_nosetests(test) 205 | -------------------------------------------------------------------------------- /mpc_orca/launch/start_controllers_world1.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 15 | 16 | 17 | 22 | 23 | -------------------------------------------------------------------------------- /mpc_orca/launch/start_controllers_world2.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 19 | 20 | 22 | 23 | 25 | 26 | 27 | 32 | 33 | -------------------------------------------------------------------------------- /mpc_orca/launch/start_controllers_world3.launch: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 19 | 20 | 22 | 23 | 25 | 26 | 27 | 32 | 33 | -------------------------------------------------------------------------------- /mpc_orca/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | mpc_orca 4 | 0.0.0 5 | The mpc_orca package 6 | 7 | 8 | 9 | 10 | glauberrleite 11 | 12 | 13 | 14 | 15 | 16 | TODO 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | catkin 52 | rospy 53 | rospy 54 | rospy 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /mpc_orca/src/MPC_ORCA.py: -------------------------------------------------------------------------------- 1 | import osqp 2 | import numpy 3 | import scipy.sparse as sparse 4 | from pyorca import Agent, orca 5 | 6 | class MPC_ORCA: 7 | 8 | def __init__(self, position, v_min, v_max, N, N_c, Ts, colliders, tau, robot_radius): 9 | """ MPC-ORCA controller instance 10 | 11 | :param goal: Goal position 12 | :type goal: Numpy Array 2x1 13 | :param position: Initial position 14 | :type position: Numpy Array 2x1 15 | :param v_min: Lower velocity constraint 16 | :type v_min: float 17 | :param v_max: Upper velocity constraint 18 | :type v_max: float 19 | :param N: Prediction Horizon 20 | :type N: int 21 | :param Ts: Sampling Time 22 | :type Ts: float 23 | :returns: Controller instance 24 | :type: MPCORCA 25 | """ 26 | 27 | self.N = N 28 | self.N_c = N_c 29 | self.Ts = Ts 30 | self.tau = tau 31 | 32 | self.agent = Agent(position, numpy.zeros(2), numpy.zeros(2), robot_radius) 33 | self.colliders = colliders 34 | 35 | # Linear Dynamics 36 | # x = [p_x, p_y, v_x, v_y]' 37 | # u = [a_x, a_y]' 38 | Ad = sparse.csc_matrix([ 39 | [1., 0., Ts, 0. ], 40 | [0., 1., 0., Ts ], 41 | [0., 0., 1., 0. ], 42 | [0., 0., 0., 1. ] 43 | ]) 44 | 45 | Bd = sparse.csc_matrix([ 46 | [0.5 * Ts ** 2, 0. ], 47 | [0., 0.5 * Ts ** 2 ], 48 | [Ts, 0. ], 49 | [0., Ts ] 50 | ]) 51 | [self.nx, self.nu] = Bd.shape 52 | 53 | # State constraints 54 | xmin = numpy.array([-numpy.inf, -numpy.inf, v_min, v_min]) 55 | xmax = numpy.array([numpy.inf, numpy.inf, v_max, v_max]) 56 | umin = numpy.array([-numpy.inf, -numpy.inf]) 57 | umax = numpy.array([numpy.inf, numpy.inf]) 58 | 59 | # Initial state 60 | x_0 = numpy.array([position[0], position[1], 0., 0.]) 61 | 62 | # Setpoint 63 | x_r = x_0 64 | 65 | # MPC objective function 66 | #Q_0 = sparse.diags([100.0, 100.0, 0.0, 0.0]) 67 | Q_0 = sparse.diags([3, 3, 0.0, 0.0]) 68 | Q = sparse.diags([1.5, 1.5, 0.0, 0.0]) 69 | R = 1.5 * sparse.eye(self.nu) 70 | 71 | # Casting QP format 72 | # QP objective 73 | P = sparse.block_diag([Q, Q_0, sparse.kron(sparse.eye(N-1), Q), sparse.kron(sparse.eye(N), R)]).tocsc() 74 | self.q = numpy.hstack([-Q.dot(x_r), -Q_0.dot(x_r), numpy.kron(numpy.ones(N-1), -Q.dot(x_r)), numpy.zeros(N * self.nu)]) 75 | 76 | # QP constraints 77 | # - linear dynamics 78 | Ax = sparse.kron(sparse.eye(N+1),-sparse.eye(self.nx)) + sparse.kron(sparse.eye(N+1, k=-1), Ad) 79 | Bu = sparse.kron(sparse.vstack([sparse.csc_matrix((1, N)), sparse.eye(N)]), Bd) 80 | A_eq = sparse.hstack([Ax, Bu]) 81 | l_eq = numpy.hstack([-x_0, numpy.zeros(N*self.nx)]) 82 | u_eq = l_eq 83 | 84 | # - Control horizon constraint 85 | A_N_c = sparse.hstack([numpy.zeros((self.nu * (N - N_c), (N+1) * self.nx)), \ 86 | numpy.zeros((self.nu * (N - N_c), (N_c - 1) * self.nu)), \ 87 | -sparse.kron(numpy.ones(((N - N_c), 1)), sparse.eye(self.nu)), \ 88 | sparse.eye(self.nu * (N - N_c))]) 89 | l_N_c = numpy.zeros(self.nu * (N - N_c)) 90 | u_N_c = numpy.zeros(self.nu * (N - N_c)) 91 | 92 | # - input and state constraints 93 | A_ineq = sparse.eye((N+1) * self.nx + N * self.nu) 94 | l_ineq = numpy.hstack([numpy.kron(numpy.ones(N+1), xmin), numpy.kron(numpy.ones(N), umin)]) 95 | u_ineq = numpy.hstack([numpy.kron(numpy.ones(N+1), xmax), numpy.kron(numpy.ones(N), umax)]) 96 | 97 | # ORCA Constraints 98 | A_ORCA_data = numpy.zeros(2 * len(self.colliders) * self.N) 99 | A_ORCA_rows = numpy.zeros(2 * len(self.colliders) * self.N) 100 | A_ORCA_cols = numpy.zeros(2 * len(self.colliders) * self.N) 101 | cnt = 0 102 | for k in range(N): 103 | for i in range(len(colliders)): 104 | A_ORCA_rows[cnt] = i * N + k 105 | A_ORCA_cols[cnt] = self.nx + k * self.nx + 2 106 | 107 | A_ORCA_rows[cnt + 1] = i * N + k 108 | A_ORCA_cols[cnt + 1] = self.nx + k * self.nx + 3 109 | cnt += 2 110 | A_ORCA = sparse.csc_matrix((A_ORCA_data, (A_ORCA_rows, A_ORCA_cols)), shape=(len(colliders) * N, A_eq.shape[1])) 111 | l_ORCA = numpy.zeros(len(colliders) * N) 112 | u_ORCA = numpy.zeros(len(colliders) * N) 113 | 114 | # OSQP constraints 115 | self.A = sparse.vstack([A_eq, A_N_c, A_ineq, A_ORCA]).tocsc() 116 | self.l = numpy.hstack([l_eq, l_N_c, l_ineq, l_ORCA]) 117 | self.u = numpy.hstack([u_eq, u_N_c, u_ineq, u_ORCA]) 118 | self.Q_0 = Q_0 119 | self.Q = Q 120 | self.R = R 121 | 122 | self.orca_rows_idx = A_eq.shape[0] + A_N_c.shape[0] + A_ineq.shape[0] 123 | 124 | # Setting problem 125 | self.problem = osqp.OSQP() 126 | self.problem.setup(P, self.q, self.A, self.l, self.u, warm_start=True, verbose=False) 127 | 128 | def compute(self, setpoint): 129 | # Updating initial conditions 130 | x_0 = numpy.array([self.agent.position[0], self.agent.position[1], self.agent.velocity[0], self.agent.velocity[1]]) 131 | 132 | self.q = numpy.hstack([-self.Q.dot(setpoint[0:self.nx]), -self.Q_0.dot(setpoint[self.nx:2*self.nx]), numpy.dot(sparse.kron(sparse.eye(self.N-1), -self.Q).toarray(), setpoint[2*self.nx:]), numpy.zeros(self.N * self.nu)]) 133 | 134 | self.l[:self.nx] = -x_0 135 | self.u[:self.nx] = -x_0 136 | 137 | # Predict future states with constant velocity, i.e. no acceleration 138 | for k in range(self.N): 139 | agent_k = Agent(self.agent.position + k * self.agent.velocity * self.Ts, self.agent.velocity, numpy.zeros(2), self.agent.radius) 140 | 141 | for i, collider in enumerate(self.colliders): 142 | collider_k = Agent(collider.position + k * collider.velocity * self.Ts, collider.velocity, numpy.zeros(2), collider.radius) 143 | 144 | # Discovering ORCA half-space 145 | v0, n = orca(agent_k, collider_k, self.tau, self.Ts) 146 | 147 | self.A[self.orca_rows_idx + i * self.N + k, self.nx + k * self.nx + 2] = n[0] 148 | self.A[self.orca_rows_idx + i * self.N + k, self.nx + k * self.nx + 3] = n[1] 149 | 150 | self.l[self.orca_rows_idx + i * self.N + k] = -numpy.inf 151 | self.u[self.orca_rows_idx + i * self.N + k] = numpy.dot(n, v0) 152 | 153 | self.problem.update(q=self.q, l=self.l, u=self.u, Ax=self.A.data) 154 | result = self.problem.solve() 155 | 156 | if result.info.status == 'solved': 157 | # return the first resulting velocity after control action 158 | return [result.x[(self.nx + 2):(self.nx + 4)], result.x[-self.N*self.nu:-(self.N-1)*self.nu]] 159 | else: 160 | print(result.info.status) 161 | 162 | damping = 3 163 | new_acceleration = (1 - damping) * self.agent.acceleration 164 | return [self.agent.velocity + new_acceleration * self.Ts, new_acceleration] -------------------------------------------------------------------------------- /mpc_orca/src/controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import sys 4 | import rospy 5 | import numpy as np 6 | from gazebo_msgs.msg import ModelStates 7 | from geometry_msgs.msg import Twist, Vector3 8 | from MPC_ORCA import MPC_ORCA 9 | from pyorca import Agent 10 | 11 | # Reads robot index and goal values 12 | robot = int(sys.argv[1]) 13 | goal = np.array([float(sys.argv[2]), float(sys.argv[3])]) 14 | 15 | # Robot and controller parameters 16 | RADIUS = 0.4 17 | tau = 5 18 | N = 10 19 | N_c = N 20 | Ts = 0.1 21 | V_min = -5 22 | V_max = 5 23 | 24 | # Defining functions 25 | def updateWorld(msg): 26 | """This funcion is called whenever the gazebo/model_states publishes. This function 27 | updates the world variables as fast as possible""" 28 | for i in range(n_robots): 29 | X[i] = np.array([float(msg.pose[model[i]].position.x), float(msg.pose[model[i]].position.y)]) 30 | V[i] = np.array([float(msg.twist[model[i]].linear.x), float(msg.twist[model[i]].linear.y)]) 31 | orientation[i] = np.arctan2(2 * float(msg.pose[model[i]].orientation.w) * float(msg.pose[model[i]].orientation.z), 1 - 2 * float(msg.pose[model[i]].orientation.z)**2) 32 | 33 | def accelerationTransform(a, v, w, theta_0): 34 | """This function applies the linearization transformation on acceleration values 35 | based on equation 2.11 36 | """ 37 | d = 0.2 38 | cos_theta = np.cos(theta_0) 39 | sin_theta = np.sin(theta_0) 40 | term1 = a[0] + v * w * sin_theta + d * (w**2) * cos_theta 41 | term2 = a[1] - v * w * cos_theta + d * (w**2) * sin_theta 42 | acc_linear = cos_theta * term1 + sin_theta * term2 43 | acc_angular = -(sin_theta/d) * term1 + (cos_theta/d) * term2 44 | 45 | return [acc_linear, acc_angular] 46 | 47 | def update_positions(agents): 48 | """Each robot has a list of all agents in its neighborhood, including itself. This 49 | function updates that list, not as fast as updateWorld(). 50 | """ 51 | for i in range(n_robots): 52 | agents[i].position = np.array(X[i]) 53 | agents[i].velocity = np.array(V[i]) 54 | return agents 55 | 56 | # Initializing ros node 57 | rospy.init_node('mpc_orca_controller_robot_' + str(robot)) 58 | 59 | # Waiting gazebo first message 60 | data = rospy.wait_for_message('/gazebo/model_states', ModelStates) 61 | 62 | n_robots = len(data.name) - 1 63 | 64 | X = [np.zeros(2) for _ in range(n_robots)] 65 | V = [np.zeros(2) for _ in range(n_robots)] 66 | orientation = [0.0 for _ in range(n_robots)] 67 | model = [i+1 for i in range(n_robots)] 68 | 69 | # Getting robot model order on gazebo model_states 70 | for i, value in enumerate(data.name): 71 | # Skipping i == 0 because it's the ground_plane state 72 | if i > 0: 73 | idx = value.split('_') 74 | model[int(idx[1])] = i 75 | 76 | # Update initial X, V and orientation 77 | updateWorld(data) 78 | 79 | # Agents list 80 | agents = [] 81 | for i in range(n_robots): 82 | agents.append(Agent(X[i], np.zeros(2), np.zeros(2), RADIUS)) 83 | 84 | # Subscribing on model_states instead of robot/odom, to avoid unnecessary noise 85 | rospy.Subscriber('/gazebo/model_states', ModelStates, updateWorld) 86 | 87 | # Velocity publisher 88 | pub = rospy.Publisher('/robot_' + str(robot) + '/cmd_vel', Twist, queue_size=10) 89 | 90 | # Setpoint Publishers 91 | pub_setpoint_pos = rospy.Publisher('/robot_' + str(robot) + '/setpoint_pos', Vector3, queue_size=10) 92 | pub_setpoint_vel = rospy.Publisher('/robot_' + str(robot) + '/setpoint_vel', Vector3, queue_size=10) 93 | 94 | setpoint_pos = Vector3() 95 | setpoint_vel = Vector3() 96 | 97 | # Initializing Controllers 98 | colliders = agents[:robot] + agents[robot + 1:] 99 | controller = MPC_ORCA(agents[robot].position, V_min, V_max, N, N_c, Ts, colliders, tau, RADIUS) 100 | 101 | # Global path planning 102 | initial = np.copy(X[robot]) 103 | t_max = 10.0 104 | growth = 0.5 105 | logistic = lambda t: 1/(1 + np.exp(- growth * (t - t_max))) 106 | d_logistic = lambda t: growth * logistic(t) * (1 - logistic(t)) 107 | P_des = lambda t: goal * logistic(t) + initial * (1 - logistic(t)) 108 | V_des = lambda t: goal * d_logistic(t) - initial * d_logistic(t) 109 | 110 | t = 0 111 | 112 | vel = Twist() 113 | while not rospy.is_shutdown(): 114 | 115 | # Updating controller list of agents 116 | agents = update_positions(agents) 117 | controller.agent = agents[robot] 118 | controller.colliders = agents[:robot] + agents[robot + 1:] 119 | 120 | # Updating setpoint trajectory 121 | setpoint = np.ravel([np.append(P_des(t + k * Ts), V_des(t + k * Ts)) for k in range(0, N + 1)]) 122 | 123 | # Computing optimal input values 124 | [_, agents[robot].acceleration] = controller.compute(setpoint) 125 | 126 | # Saving setpoints 127 | [setpoint_pos.x, setpoint_pos.y] = P_des(t) 128 | [setpoint_vel.x, setpoint_vel.y] = V_des(t) 129 | 130 | # Linearization transformation for linear and angular acceleration 131 | [acc_linear, acc_angular] = accelerationTransform(agents[robot].acceleration, vel.linear.x, vel.angular.z, orientation[robot]) 132 | 133 | # Integrating acceleration to get velocity commands 134 | vel.linear.x = vel.linear.x + acc_linear * Ts 135 | vel.angular.z = vel.angular.z + acc_angular * Ts 136 | 137 | # Publishing to topics 138 | pub.publish(vel) 139 | pub_setpoint_pos.publish(setpoint_pos) 140 | pub_setpoint_vel.publish(setpoint_vel) 141 | 142 | # Sleep until the next iteration 143 | rospy.sleep(Ts) 144 | t += Ts -------------------------------------------------------------------------------- /mpc_orca/src/pyorca.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Mak Nazecic-Andrlon 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | 21 | # Adapted by @glauberrleite 22 | 23 | """Implementation of the 2D ORCA algorithm as described by J. van der Berg, 24 | S. J. Guy, M. Lin and D. Manocha in 'Reciprocal n-body Collision Avoidance'.""" 25 | 26 | from __future__ import division 27 | 28 | import numpy 29 | from numpy import array, sqrt, copysign, dot 30 | from numpy.linalg import det 31 | 32 | # Method: 33 | # For each robot A and potentially colliding robot B, compute smallest change 34 | # in relative velocity 'u' that avoids collision. Find normal 'n' to VO at that 35 | # point. 36 | # For each such velocity 'u' and normal 'n', find half-plane as defined in (6). 37 | # Intersect half-planes and pick velocity closest to A's preferred velocity. 38 | 39 | class Agent(object): 40 | """A disk-shaped agent.""" 41 | def __init__(self, position, velocity, acceleration, radius): 42 | super(Agent, self).__init__() 43 | self.position = array(position) 44 | self.velocity = array(velocity) 45 | self.radius = radius 46 | self.acceleration = acceleration 47 | 48 | 49 | def orca(agent, colliding_agent, t, dt): 50 | """Compute ORCA solution for agent. NOTE: velocity must be _instantly_ 51 | changed on tick *edge*, like first-order integration, otherwise the method 52 | undercompensates and you will still risk colliding.""" 53 | dv, n = get_avoidance_velocity(agent, colliding_agent, t, dt) 54 | v0 = agent.velocity + dv / 2 55 | 56 | return v0, n 57 | 58 | def get_avoidance_velocity(agent, collider, t, dt): 59 | """Get the smallest relative change in velocity between agent and collider 60 | that will get them onto the boundary of each other's velocity obstacle 61 | (VO), and thus avert collision.""" 62 | 63 | # This is a summary of the explanation from the AVO paper. 64 | # 65 | # The set of all relative velocities that will cause a collision within 66 | # time tau is called the velocity obstacle (VO). If the relative velocity 67 | # is outside of the VO, no collision will happen for at least tau time. 68 | # 69 | # The VO for two moving disks is a circularly truncated triangle 70 | # (spherically truncated cone in 3D), with an imaginary apex at the 71 | # origin. It can be described by a union of disks: 72 | # 73 | # Define an open disk centered at p with radius r: 74 | # D(p, r) := {q | ||q - p|| < r} (1) 75 | # 76 | # Two disks will collide at time t iff ||x + vt|| < r, where x is the 77 | # displacement, v is the relative velocity, and r is the sum of their 78 | # radii. 79 | # 80 | # Divide by t: ||x/t + v|| < r/t, 81 | # Rearrange: ||v - (-x/t)|| < r/t. 82 | # 83 | # By (1), this is a disk D(-x/t, r/t), and it is the set of all velocities 84 | # that will cause a collision at time t. 85 | # 86 | # We can now define the VO for time tau as the union of all such disks 87 | # D(-x/t, r/t) for 0 < t <= tau. 88 | # 89 | # Note that the displacement and radius scale _inversely_ proportionally 90 | # to t, generating a line of disks of increasing radius starting at -x/t. 91 | # This is what gives the VO its cone shape. The _closest_ velocity disk is 92 | # at D(-x/tau, r/tau), and this truncates the VO. 93 | 94 | x = -(agent.position - collider.position) 95 | v = agent.velocity - collider.velocity 96 | r = agent.radius + collider.radius 97 | 98 | x_len_sq = norm_sq(x) 99 | 100 | if x_len_sq >= r * r: 101 | # We need to decide whether to project onto the disk truncating the VO 102 | # or onto the sides. 103 | # 104 | # The center of the truncating disk doesn't mark the line between 105 | # projecting onto the sides or the disk, since the sides are not 106 | # parallel to the displacement. We need to bring it a bit closer. How 107 | # much closer can be worked out by similar triangles. It works out 108 | # that the new point is at x/t cos(theta)^2, where theta is the angle 109 | # of the aperture (so sin^2(theta) = (r/||x||)^2). 110 | adjusted_center = x/t * (1 - (r*r)/x_len_sq) 111 | 112 | if dot(v - adjusted_center, adjusted_center) < 0: 113 | # v lies in the front part of the cone 114 | # print("front") 115 | # print("front", adjusted_center, x_len_sq, r, x, t) 116 | w = v - x/t 117 | u = normalized(w) * r/t - w 118 | n = normalized(w) 119 | else: # v lies in the rest of the cone 120 | # print("sides") 121 | # Rotate x in the direction of v, to make it a side of the cone. 122 | # Then project v onto that, and calculate the difference. 123 | leg_len = sqrt(x_len_sq - r*r) 124 | # The sign of the sine determines which side to project on. 125 | sine = copysign(r, det((v, x))) 126 | rot = array( 127 | ((leg_len, sine), 128 | (-sine, leg_len))) 129 | rotated_x = rot.dot(x) / x_len_sq 130 | n = perp(rotated_x) 131 | if sine < 0: 132 | # Need to flip the direction of the line to make the 133 | # half-plane point out of the cone. 134 | n = -n 135 | # print("rotated_x=%s" % rotated_x) 136 | u = rotated_x * dot(v, rotated_x) - v 137 | # print("u=%s" % u) 138 | else: 139 | # We're already intersecting. Pick the closest velocity to our 140 | # velocity that will get us out of the collision within the next 141 | # timestep. 142 | # print("intersecting") 143 | w = v - x/dt 144 | u = normalized(w) * r/dt - w 145 | n = normalized(w) 146 | return u, -n 147 | 148 | def norm_sq(x): 149 | return dot(x, x) 150 | 151 | def normalized(x): 152 | l = norm_sq(x) 153 | assert l > 0, (x, l) 154 | return x / sqrt(l) 155 | 156 | def dist_sq(a, b): 157 | return norm_sq(b - a) 158 | 159 | def perp(a): 160 | return array((a[1], -a[0])) -------------------------------------------------------------------------------- /mpc_orca/src/test_controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import rospy 4 | import numpy 5 | from nav_msgs.msg import Odometry 6 | from geometry_msgs.msg import Twist 7 | from MPC_ORCA import MPC_ORCA 8 | 9 | N_AGENTS = 0 10 | RADIUS = 0.7 11 | tau = 5 12 | 13 | Ts = 0.1 14 | X = [(-5, 0)] 15 | V = [(0, 0) for _ in xrange(len(X))] 16 | V_max = [1.0 for _ in xrange(len(X))] 17 | goal = [(5.0, 5.0)] 18 | 19 | 20 | def callback_0(msg): 21 | X[0] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 22 | V[0] = (float(msg.twist.twist.linear.x), float(msg.twist.twist.linear.y)) 23 | 24 | rospy.init_node('test_controller') 25 | 26 | sub_0 = rospy.Subscriber('/odom', Odometry, callback_0) 27 | pub_0 = rospy.Publisher('/cmd_vel', Twist, queue_size=10) 28 | t = 0 29 | 30 | controller = MPC_ORCA(goal[0], X[0], -V_max[0], V_max[0], 10, Ts) 31 | 32 | while not rospy.is_shutdown(): 33 | 34 | a_new = controller.getNewAcceleration(X[0], V[0]) 35 | velocity = V[0] + a_new * Ts 36 | 37 | vel_0 = Twist() 38 | vel_0.linear.x = velocity[0] 39 | vel_0.linear.y = velocity[1] 40 | 41 | 42 | pub_0.publish(vel_0) 43 | 44 | if t%100: 45 | print(velocity) 46 | print(X[0]) 47 | t += 1 48 | rospy.sleep(Ts) 49 | -------------------------------------------------------------------------------- /orca/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.3) 2 | project(orca) 3 | 4 | ## Compile as C++11, supported in ROS Kinetic and newer 5 | # add_compile_options(-std=c++11) 6 | 7 | ## Find catkin macros and libraries 8 | ## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) 9 | ## is used, also find other catkin packages 10 | find_package(catkin REQUIRED COMPONENTS 11 | rospy 12 | ) 13 | 14 | ## System dependencies are found with CMake's conventions 15 | # find_package(Boost REQUIRED COMPONENTS system) 16 | 17 | 18 | ## Uncomment this if the package has a setup.py. This macro ensures 19 | ## modules and global scripts declared therein get installed 20 | ## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html 21 | # catkin_python_setup() 22 | 23 | ################################################ 24 | ## Declare ROS messages, services and actions ## 25 | ################################################ 26 | 27 | ## To declare and build messages, services or actions from within this 28 | ## package, follow these steps: 29 | ## * Let MSG_DEP_SET be the set of packages whose message types you use in 30 | ## your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...). 31 | ## * In the file package.xml: 32 | ## * add a build_depend tag for "message_generation" 33 | ## * add a build_depend and a exec_depend tag for each package in MSG_DEP_SET 34 | ## * If MSG_DEP_SET isn't empty the following dependency has been pulled in 35 | ## but can be declared for certainty nonetheless: 36 | ## * add a exec_depend tag for "message_runtime" 37 | ## * In this file (CMakeLists.txt): 38 | ## * add "message_generation" and every package in MSG_DEP_SET to 39 | ## find_package(catkin REQUIRED COMPONENTS ...) 40 | ## * add "message_runtime" and every package in MSG_DEP_SET to 41 | ## catkin_package(CATKIN_DEPENDS ...) 42 | ## * uncomment the add_*_files sections below as needed 43 | ## and list every .msg/.srv/.action file to be processed 44 | ## * uncomment the generate_messages entry below 45 | ## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...) 46 | 47 | ## Generate messages in the 'msg' folder 48 | # add_message_files( 49 | # FILES 50 | # Message1.msg 51 | # Message2.msg 52 | # ) 53 | 54 | ## Generate services in the 'srv' folder 55 | # add_service_files( 56 | # FILES 57 | # Service1.srv 58 | # Service2.srv 59 | # ) 60 | 61 | ## Generate actions in the 'action' folder 62 | # add_action_files( 63 | # FILES 64 | # Action1.action 65 | # Action2.action 66 | # ) 67 | 68 | ## Generate added messages and services with any dependencies listed here 69 | # generate_messages( 70 | # DEPENDENCIES 71 | # std_msgs # Or other packages containing msgs 72 | # ) 73 | 74 | ################################################ 75 | ## Declare ROS dynamic reconfigure parameters ## 76 | ################################################ 77 | 78 | ## To declare and build dynamic reconfigure parameters within this 79 | ## package, follow these steps: 80 | ## * In the file package.xml: 81 | ## * add a build_depend and a exec_depend tag for "dynamic_reconfigure" 82 | ## * In this file (CMakeLists.txt): 83 | ## * add "dynamic_reconfigure" to 84 | ## find_package(catkin REQUIRED COMPONENTS ...) 85 | ## * uncomment the "generate_dynamic_reconfigure_options" section below 86 | ## and list every .cfg file to be processed 87 | 88 | ## Generate dynamic reconfigure parameters in the 'cfg' folder 89 | # generate_dynamic_reconfigure_options( 90 | # cfg/DynReconf1.cfg 91 | # cfg/DynReconf2.cfg 92 | # ) 93 | 94 | ################################### 95 | ## catkin specific configuration ## 96 | ################################### 97 | ## The catkin_package macro generates cmake config files for your package 98 | ## Declare things to be passed to dependent projects 99 | ## INCLUDE_DIRS: uncomment this if your package contains header files 100 | ## LIBRARIES: libraries you create in this project that dependent projects also need 101 | ## CATKIN_DEPENDS: catkin_packages dependent projects also need 102 | ## DEPENDS: system dependencies of this project that dependent projects also need 103 | catkin_package( 104 | # INCLUDE_DIRS include 105 | # LIBRARIES orca 106 | # CATKIN_DEPENDS rospy 107 | # DEPENDS system_lib 108 | ) 109 | 110 | ########### 111 | ## Build ## 112 | ########### 113 | 114 | ## Specify additional locations of header files 115 | ## Your package locations should be listed before other locations 116 | include_directories( 117 | # include 118 | ${catkin_INCLUDE_DIRS} 119 | ) 120 | 121 | ## Declare a C++ library 122 | # add_library(${PROJECT_NAME} 123 | # src/${PROJECT_NAME}/orca.cpp 124 | # ) 125 | 126 | ## Add cmake target dependencies of the library 127 | ## as an example, code may need to be generated before libraries 128 | ## either from message generation or dynamic reconfigure 129 | # add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) 130 | 131 | ## Declare a C++ executable 132 | ## With catkin_make all packages are built within a single CMake context 133 | ## The recommended prefix ensures that target names across packages don't collide 134 | # add_executable(${PROJECT_NAME}_node src/orca_node.cpp) 135 | 136 | ## Rename C++ executable without prefix 137 | ## The above recommended prefix causes long target names, the following renames the 138 | ## target back to the shorter version for ease of user use 139 | ## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node" 140 | # set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "") 141 | 142 | ## Add cmake target dependencies of the executable 143 | ## same as for the library above 144 | # add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) 145 | 146 | ## Specify libraries to link a library or executable target against 147 | # target_link_libraries(${PROJECT_NAME}_node 148 | # ${catkin_LIBRARIES} 149 | # ) 150 | 151 | ############# 152 | ## Install ## 153 | ############# 154 | 155 | # all install targets should use catkin DESTINATION variables 156 | # See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html 157 | 158 | ## Mark executable scripts (Python etc.) for installation 159 | ## in contrast to setup.py, you can choose the destination 160 | # install(PROGRAMS 161 | # scripts/my_python_script 162 | # DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} 163 | # ) 164 | 165 | ## Mark executables and/or libraries for installation 166 | # install(TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_node 167 | # ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} 168 | # LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} 169 | # RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} 170 | # ) 171 | 172 | ## Mark cpp header files for installation 173 | # install(DIRECTORY include/${PROJECT_NAME}/ 174 | # DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} 175 | # FILES_MATCHING PATTERN "*.h" 176 | # PATTERN ".svn" EXCLUDE 177 | # ) 178 | 179 | ## Mark other files for installation (e.g. launch and bag files, etc.) 180 | # install(FILES 181 | # # myfile1 182 | # # myfile2 183 | # DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} 184 | # ) 185 | 186 | ############# 187 | ## Testing ## 188 | ############# 189 | 190 | ## Add gtest based cpp test target and link libraries 191 | # catkin_add_gtest(${PROJECT_NAME}-test test/test_orca.cpp) 192 | # if(TARGET ${PROJECT_NAME}-test) 193 | # target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME}) 194 | # endif() 195 | 196 | ## Add folders to be run by python nosetests 197 | # catkin_add_nosetests(test) 198 | -------------------------------------------------------------------------------- /orca/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | orca 4 | 0.0.0 5 | The orca package 6 | 7 | 8 | 9 | 10 | glauberrleite 11 | 12 | 13 | 14 | 15 | 16 | TODO 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | catkin 52 | rospy 53 | rospy 54 | rospy 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /orca/src/controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | # @author glauberrleite 4 | 5 | import rospy 6 | 7 | import numpy as np 8 | 9 | from RVO import RVO_update, reach, compute_V_des, reach 10 | from geometry_msgs.msg import Twist 11 | from gazebo_msgs.msg import ModelStates 12 | from kobuki_msgs.msg import MotorPower 13 | 14 | scenario = dict() 15 | scenario['robot_radius'] = 0.8 16 | scenario['circular_obstacles'] = [] 17 | scenario['boundary'] = [] 18 | 19 | Ts = 0.01 20 | X = [[0,0], [10,0]] 21 | orientation = [0, np.pi] 22 | V = [[0,0] for _ in xrange(len(X))] 23 | V_max = [1.0 for i in xrange(len(X))] 24 | goal = [[10, 0], [0,0]] 25 | 26 | def updateWorld(msg): 27 | X[0] = [float(msg.pose[1].position.x), float(msg.pose[1].position.y)] 28 | orientation[0] = 2* np.arctan2(float(msg.pose[1].orientation.z), float(msg.pose[1].orientation.w)) 29 | if np.abs(orientation[0]) > np.pi: 30 | orientation[0] = orientation[0] - np.sign(orientation[0]) * 2 * np.pi 31 | 32 | X[1] = [float(msg.pose[2].position.x), float(msg.pose[2].position.y)] 33 | orientation[1] = 2* np.arctan2(float(msg.pose[2].orientation.z), float(msg.pose[2].orientation.w)) 34 | if np.abs(orientation[1]) > np.pi: 35 | orientation[1] = orientation[1] - np.sign(orientation[1]) * 2 * np.pi 36 | 37 | 38 | rospy.init_node('rvo_controller') 39 | pub_vel_r0 = rospy.Publisher("robot0/mobile_base/commands/velocity", Twist, queue_size=10); 40 | pub_vel_r1 = rospy.Publisher("robot1/mobile_base/commands/velocity", Twist, queue_size=10); 41 | pub_mot_r0 = rospy.Publisher("robot0/mobile_base/commands/motor_power", MotorPower, queue_size=10); 42 | pub_mot_r1 = rospy.Publisher("robot1/mobile_base/commands/motor_power", MotorPower, queue_size=10); 43 | 44 | rospy.Subscriber('/gazebo/model_states', ModelStates, updateWorld) 45 | 46 | motor0 = MotorPower() 47 | motor1 = MotorPower() 48 | motor0.state = motor0.ON 49 | motor1.state = motor1.ON 50 | 51 | pub_mot_r0.publish(motor0) 52 | pub_mot_r1.publish(motor1) 53 | 54 | t = 0 55 | 56 | while not rospy.is_shutdown(): 57 | # compute desired vel to goal 58 | V_des = compute_V_des(X, goal, V_max) 59 | # compute the optimal vel to avoid collision 60 | V = RVO_update(X, V_des, V, scenario) 61 | 62 | # Compute ang_vel considering singularities 63 | angular_vel_0 = (np.arctan2(V[0][1], V[0][0]) - orientation[0]) 64 | if np.abs(angular_vel_0) > np.pi: 65 | angular_vel_0 = (orientation[0] - np.arctan2(V[0][1], V[0][0])) 66 | 67 | linear_vel_0 = np.sqrt(V[0][0]**2 + V[0][1]**2) 68 | vel_rob0 = Twist() 69 | vel_rob0.linear.x = linear_vel_0 70 | vel_rob0.linear.y = 0.0 71 | vel_rob0.linear.z = 0.0 72 | vel_rob0.angular.x = 0.0 73 | vel_rob0.angular.y = 0.0 74 | vel_rob0.angular.z = angular_vel_0 75 | 76 | angular_vel_1 = (np.arctan2(V[1][1], V[1][0]) - orientation[1]) 77 | if np.abs(angular_vel_1) > np.pi: 78 | angular_vel_1 = (orientation[0] - np.arctan2(V[1][1], V[1][0])) 79 | 80 | linear_vel_1 = np.sqrt(V[1][0]**2 + V[1][1]**2) 81 | vel_rob1 = Twist() 82 | vel_rob1.linear.x = linear_vel_1 83 | vel_rob1.linear.y = 0.0; 84 | vel_rob1.linear.z = 0.0; 85 | vel_rob1.angular.x = 0.0; 86 | vel_rob1.angular.y = 0.0; 87 | vel_rob1.angular.z = angular_vel_1 88 | 89 | pub_vel_r0.publish(vel_rob0) 90 | pub_vel_r1.publish(vel_rob1) 91 | 92 | #if t%100 == 0: 93 | # print(V_des[1]) 94 | # print(vel_rob1.linear.x) 95 | # print(vel_rob1.angular.z) 96 | # print(orientation) 97 | # print('---------') 98 | #t = t + 1 99 | 100 | rospy.sleep(Ts) 101 | -------------------------------------------------------------------------------- /orca/src/dense_diff_controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import rospy 4 | import numpy as np 5 | from nav_msgs.msg import Odometry 6 | from rosgraph_msgs.msg import Clock 7 | from geometry_msgs.msg import Twist, Vector3 8 | from pyorca import Agent, orca, normalized 9 | 10 | N_AGENTS = 4 11 | RADIUS = 0.7 12 | tau = 15 13 | 14 | Ts = 0.01 15 | X = [[-10., 0.25], [10., 0.], [0.25, 10.], [0., -10.], [-10.25, 10.], [-10., -10.], [10.0, -10.25], [10., 10.]] 16 | orientation = [0, np.pi, -np.pi/2, np.pi/2, -np.pi/4, np.pi/4, 3*np.pi/4, -3*np.pi/4] 17 | V = [[0., 0.] for _ in xrange(len(X))] 18 | V_min = [-1.0 for _ in xrange(len(X))] 19 | V_max = [1.0 for _ in xrange(len(X))] 20 | goal = [[10., 0.], [-10., 0.], [0.0, -10.], [0., 10.], [10., -10.], [10., 10.], [-10.0, 10.], [-10., -10.]] 21 | 22 | agents = [] 23 | 24 | 25 | def velocityTransform(v, theta_0): 26 | angular = np.arctan2(v[1], v[0]) - theta_0 27 | linear = np.sqrt(v[0]**2 + v[1]**2) 28 | 29 | # Handling singularity 30 | if np.abs(angular) > np.pi: 31 | angular -= np.sign(angular) * 2 * np.pi 32 | 33 | return [linear, angular] 34 | for i in xrange(len(X)): 35 | vel = (np.array(goal[i]) - np.array(X[i])) 36 | if np.linalg.norm(vel) > V_max[i]: 37 | vel = normalized(vel) * V_max[i] 38 | agents.append(Agent(X[i], (0., 0.), RADIUS, V_max[i], vel)) 39 | 40 | def update_agents(agents): 41 | for i in xrange(len(X)): 42 | agents[i].pref_velocity = (np.array(goal[i]) - np.array(X[i])) 43 | if np.linalg.norm(agents[i].pref_velocity) > V_max[i]: 44 | agents[i].pref_velocity = normalized(agents[i].pref_velocity) * V_max[i] 45 | return agents 46 | 47 | def callback_0(msg): 48 | X[0] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 49 | orientation[0] = 2 * np.arctan2(float(msg.pose.pose.orientation.z), float(msg.pose.pose.orientation.w)) 50 | def callback_1(msg): 51 | X[1] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 52 | orientation[1] = 2 * np.arctan2(float(msg.pose.pose.orientation.z), float(msg.pose.pose.orientation.w)) 53 | def callback_2(msg): 54 | X[2] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 55 | orientation[2] = 2 * np.arctan2(float(msg.pose.pose.orientation.z), float(msg.pose.pose.orientation.w)) 56 | def callback_3(msg): 57 | X[3] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 58 | orientation[3] = 2 * np.arctan2(float(msg.pose.pose.orientation.z), float(msg.pose.pose.orientation.w)) 59 | def callback_4(msg): 60 | X[4] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 61 | orientation[4] = 2 * np.arctan2(float(msg.pose.pose.orientation.z), float(msg.pose.pose.orientation.w)) 62 | def callback_5(msg): 63 | X[5] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 64 | orientation[5] = 2 * np.arctan2(float(msg.pose.pose.orientation.z), float(msg.pose.pose.orientation.w)) 65 | def callback_6(msg): 66 | X[6] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 67 | orientation[6] = 2 * np.arctan2(float(msg.pose.pose.orientation.z), float(msg.pose.pose.orientation.w)) 68 | def callback_7(msg): 69 | X[7] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 70 | orientation[7] = 2 * np.arctan2(float(msg.pose.pose.orientation.z), float(msg.pose.pose.orientation.w)) 71 | 72 | rospy.init_node('diff_controller') 73 | 74 | sub_0 = rospy.Subscriber('/robot_0/odom', Odometry, callback_0) 75 | sub_1 = rospy.Subscriber('/robot_1/odom', Odometry, callback_1) 76 | sub_2 = rospy.Subscriber('/robot_2/odom', Odometry, callback_2) 77 | sub_3 = rospy.Subscriber('/robot_3/odom', Odometry, callback_3) 78 | sub_4 = rospy.Subscriber('/robot_4/odom', Odometry, callback_4) 79 | sub_5 = rospy.Subscriber('/robot_5/odom', Odometry, callback_5) 80 | sub_6 = rospy.Subscriber('/robot_6/odom', Odometry, callback_6) 81 | sub_7 = rospy.Subscriber('/robot_7/odom', Odometry, callback_7) 82 | pub = [] 83 | pub.append(rospy.Publisher('/robot_0/cmd_vel', Twist, queue_size=10)) 84 | pub.append(rospy.Publisher('/robot_1/cmd_vel', Twist, queue_size=10)) 85 | pub.append(rospy.Publisher('/robot_2/cmd_vel', Twist, queue_size=10)) 86 | pub.append(rospy.Publisher('/robot_3/cmd_vel', Twist, queue_size=10)) 87 | pub.append(rospy.Publisher('/robot_4/cmd_vel', Twist, queue_size=10)) 88 | pub.append(rospy.Publisher('/robot_5/cmd_vel', Twist, queue_size=10)) 89 | pub.append(rospy.Publisher('/robot_6/cmd_vel', Twist, queue_size=10)) 90 | pub.append(rospy.Publisher('/robot_7/cmd_vel', Twist, queue_size=10)) 91 | pub_setpoint = rospy.Publisher('/setpoint', Vector3, queue_size=10) 92 | 93 | setpoint = Vector3() 94 | setpoint.x = goal[4][0] 95 | setpoint.y = goal[4][1] 96 | 97 | rospy.wait_for_message('/clock', Clock) 98 | 99 | while not rospy.is_shutdown(): 100 | agents = update_agents(agents) 101 | for i, agent in enumerate(agents): 102 | candidates = agents[:i] + agents[i + 1:] 103 | agent.velocity, _ = orca(agent, candidates, tau, Ts) 104 | 105 | for i in xrange(len(X)): 106 | vel = Twist() 107 | [vel.linear.x, vel.angular.z] = velocityTransform(agents[i].velocity, orientation[i]) 108 | 109 | pub[i].publish(vel) 110 | 111 | pub_setpoint.publish(setpoint) 112 | rospy.sleep(Ts) 113 | -------------------------------------------------------------------------------- /orca/src/dense_omni_controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import rospy 4 | import numpy 5 | from nav_msgs.msg import Odometry 6 | from geometry_msgs.msg import Twist, Vector3 7 | from rosgraph_msgs.msg import Clock 8 | from pyorca import Agent, orca, normalized 9 | 10 | RADIUS = 0.5 11 | tau = 15 12 | 13 | Ts = 0.01 14 | X = [[-10., 0.25], [10., 0.], [0.0, 10.], [-0.25, -10.], [-10., 10.25], [-10., -10.25], [10.0, -10.], [10., 10.]] 15 | V = [[0., 0.] for _ in xrange(len(X))] 16 | V_max = [1.0 for _ in xrange(len(X))] 17 | goal = [[10., 0.], [-10., 0.], [0.0, -10.], [0., 10.], [10., -10.], [10., 10.], [-10.0, 10.], [-10., -10.]] 18 | 19 | agents = [] 20 | 21 | for i in xrange(len(X)): 22 | vel = (numpy.array(goal[i]) - numpy.array(X[i])) 23 | if numpy.linalg.norm(vel) > V_max[i]: 24 | vel = normalized(vel) * V_max[i] 25 | agents.append(Agent(X[i], (0., 0.), RADIUS, V_max[i], vel)) 26 | 27 | def update_agents(agents): 28 | for i in xrange(len(X)): 29 | agents[i].pref_velocity = (numpy.array(goal[i]) - numpy.array(X[i])) 30 | if numpy.linalg.norm(agents[i].pref_velocity) > V_max[i]: 31 | agents[i].pref_velocity = normalized(agents[i].pref_velocity) * V_max[i] 32 | return agents 33 | 34 | def callback_0(msg): 35 | X[0] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 36 | def callback_1(msg): 37 | X[1] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 38 | def callback_2(msg): 39 | X[2] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 40 | def callback_3(msg): 41 | X[3] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 42 | def callback_4(msg): 43 | X[4] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 44 | def callback_5(msg): 45 | X[5] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 46 | def callback_6(msg): 47 | X[6] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 48 | def callback_7(msg): 49 | X[7] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 50 | 51 | 52 | rospy.init_node('omni_controller') 53 | 54 | sub_0 = rospy.Subscriber('/robot_0/odom', Odometry, callback_0) 55 | sub_1 = rospy.Subscriber('/robot_1/odom', Odometry, callback_1) 56 | sub_2 = rospy.Subscriber('/robot_2/odom', Odometry, callback_2) 57 | sub_3 = rospy.Subscriber('/robot_3/odom', Odometry, callback_3) 58 | sub_4 = rospy.Subscriber('/robot_4/odom', Odometry, callback_4) 59 | sub_5 = rospy.Subscriber('/robot_5/odom', Odometry, callback_5) 60 | sub_6 = rospy.Subscriber('/robot_6/odom', Odometry, callback_6) 61 | sub_7 = rospy.Subscriber('/robot_7/odom', Odometry, callback_7) 62 | pub = [] 63 | pub.append(rospy.Publisher('/robot_0/cmd_vel', Twist, queue_size=10)) 64 | pub.append(rospy.Publisher('/robot_1/cmd_vel', Twist, queue_size=10)) 65 | pub.append(rospy.Publisher('/robot_2/cmd_vel', Twist, queue_size=10)) 66 | pub.append(rospy.Publisher('/robot_3/cmd_vel', Twist, queue_size=10)) 67 | pub.append(rospy.Publisher('/robot_4/cmd_vel', Twist, queue_size=10)) 68 | pub.append(rospy.Publisher('/robot_5/cmd_vel', Twist, queue_size=10)) 69 | pub.append(rospy.Publisher('/robot_6/cmd_vel', Twist, queue_size=10)) 70 | pub.append(rospy.Publisher('/robot_7/cmd_vel', Twist, queue_size=10)) 71 | pub_setpoint = rospy.Publisher('/setpoint', Vector3, queue_size=10) 72 | t = 0 73 | 74 | setpoint = Vector3() 75 | setpoint.x = goal[4][0] 76 | setpoint.y = goal[4][1] 77 | 78 | rospy.wait_for_message('/clock', Clock) 79 | while not rospy.is_shutdown(): 80 | 81 | agents = update_agents(agents) 82 | 83 | for i, agent in enumerate(agents): 84 | candidates = agents[:i] + agents[i + 1:] 85 | agent.velocity, _ = orca(agent, candidates, tau, Ts) 86 | 87 | for i in xrange(len(X)): 88 | vel = Twist() 89 | vel.linear.x = agents[i].velocity[0] 90 | vel.linear.y = agents[i].velocity[1] 91 | 92 | pub[i].publish(vel) 93 | 94 | pub_setpoint.publish(setpoint) 95 | 96 | if t%100: 97 | #print(X) 98 | pass 99 | t += 1 100 | rospy.sleep(Ts) 101 | -------------------------------------------------------------------------------- /orca/src/diff_omni_controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import rospy 4 | import numpy as np 5 | from nav_msgs.msg import Odometry 6 | from geometry_msgs.msg import Twist 7 | from pyorca import Agent, orca, normalized 8 | 9 | N_AGENTS = 4 10 | RADIUS = 0.7 11 | tau = 5 12 | 13 | Ts = 0.01 14 | X = [(-5, 0.25), (5, 0), (0.00, 5), (-0.25, -5)] 15 | orientation = [0, np.pi, -np.pi/2, np.pi/2] 16 | V = [(0, 0) for _ in xrange(len(X))] 17 | V_max = [1.0 for _ in xrange(len(X))] 18 | goal = [(5.0, 0.0), (-5.0, 0.0), (0.0, -5.0), (0.0, 5.0)] 19 | 20 | agents = [] 21 | 22 | 23 | def velocityTransform(v, theta_0): 24 | angular = np.arctan2(v[1], v[0]) - theta_0 25 | linear = np.sqrt(v[0]**2 + v[1]**2) 26 | 27 | # Handling singularity 28 | if np.abs(angular) > np.pi: 29 | angular -= np.sign(angular) * 2 * np.pi 30 | 31 | return [linear, angular] 32 | for i in xrange(len(X)): 33 | vel = (np.array(goal[i]) - np.array(X[i])) 34 | if np.linalg.norm(vel) > V_max[i]: 35 | vel = normalized(vel) * V_max[i] 36 | agents.append(Agent(X[i], (0., 0.), RADIUS, V_max[i], vel)) 37 | 38 | def update_agents(agents): 39 | for i in xrange(len(X)): 40 | agents[i].pref_velocity = (np.array(goal[i]) - np.array(X[i])) 41 | if np.linalg.norm(agents[i].pref_velocity) > V_max[i]: 42 | agents[i].pref_velocity = normalized(agents[i].pref_velocity) * V_max[i] 43 | return agents 44 | 45 | def callback_0(msg): 46 | X[0] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 47 | orientation[0] = 2 * np.arctan2(float(msg.pose.pose.orientation.z), float(msg.pose.pose.orientation.w)) 48 | def callback_1(msg): 49 | X[1] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 50 | orientation[1] = 2 * np.arctan2(float(msg.pose.pose.orientation.z), float(msg.pose.pose.orientation.w)) 51 | def callback_2(msg): 52 | X[2] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 53 | orientation[2] = 2 * np.arctan2(float(msg.pose.pose.orientation.z), float(msg.pose.pose.orientation.w)) 54 | def callback_3(msg): 55 | X[3] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 56 | orientation[3] = 2 * np.arctan2(float(msg.pose.pose.orientation.z), float(msg.pose.pose.orientation.w)) 57 | 58 | 59 | rospy.init_node('omni_controller') 60 | 61 | sub_0 = rospy.Subscriber('/robot_0/odom', Odometry, callback_0) 62 | sub_1 = rospy.Subscriber('/robot_1/odom', Odometry, callback_1) 63 | sub_2 = rospy.Subscriber('/robot_2/odom', Odometry, callback_2) 64 | sub_3 = rospy.Subscriber('/robot_3/odom', Odometry, callback_3) 65 | pub_0 = rospy.Publisher('/robot_0/cmd_vel', Twist, queue_size=10) 66 | pub_1 = rospy.Publisher('/robot_1/cmd_vel', Twist, queue_size=10) 67 | pub_2 = rospy.Publisher('/robot_2/cmd_vel', Twist, queue_size=10) 68 | pub_3 = rospy.Publisher('/robot_3/cmd_vel', Twist, queue_size=10) 69 | t = 0 70 | 71 | while not rospy.is_shutdown(): 72 | agents = update_agents(agents) 73 | for i, agent in enumerate(agents): 74 | candidates = agents[:i] + agents[i + 1:] 75 | agent.velocity, _ = orca(agent, candidates, tau, Ts) 76 | 77 | vel_0 = Twist() 78 | 79 | [linear_vel_0, angular_vel_0] = velocityTransform(agents[0].velocity, orientation[0]) 80 | 81 | vel_0.linear.x = linear_vel_0 82 | vel_0.angular.z = angular_vel_0 83 | 84 | vel_1 = Twist() 85 | 86 | [linear_vel_1, angular_vel_1] = velocityTransform(agents[1].velocity, orientation[1]) 87 | 88 | vel_1.linear.x = linear_vel_1 89 | vel_1.angular.z = angular_vel_1 90 | 91 | vel_2 = Twist() 92 | 93 | [linear_vel_2, angular_vel_2] = velocityTransform(agents[2].velocity, orientation[2]) 94 | 95 | vel_2.linear.x = linear_vel_2 96 | vel_2.angular.z = angular_vel_2 97 | 98 | vel_3 = Twist() 99 | 100 | [linear_vel_3, angular_vel_3] = velocityTransform(agents[3].velocity, orientation[3]) 101 | 102 | vel_3.linear.x = linear_vel_3 103 | vel_3.angular.z = angular_vel_3 104 | 105 | pub_0.publish(vel_0) 106 | pub_1.publish(vel_1) 107 | pub_2.publish(vel_2) 108 | pub_3.publish(vel_3) 109 | 110 | if t%100: 111 | print(X) 112 | #print(agents[0].velocity) 113 | t += 1 114 | rospy.sleep(Ts) 115 | -------------------------------------------------------------------------------- /orca/src/halfplaneintersect.py: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # 3 | # Copyright (c) 2013 Mak Nazecic-Andrlon 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to 7 | # deal in the Software without restriction, including without limitation the 8 | # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 9 | # sell copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in 13 | # all copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21 | # IN THE SOFTWARE. 22 | 23 | """This module solves 2D linear programming using half-plane intersection.""" 24 | 25 | from __future__ import division 26 | 27 | import itertools 28 | 29 | from numpy import dot, clip, array, sqrt 30 | from numpy.linalg import det 31 | 32 | 33 | class InfeasibleError(RuntimeError): 34 | """Raised if an LP problem has no solution.""" 35 | pass 36 | 37 | 38 | class Line(object): 39 | """A line in space.""" 40 | def __init__(self, point, direction): 41 | super(Line, self).__init__() 42 | self.point = array(point) 43 | self.direction = normalized(array(direction)) 44 | 45 | def __repr__(self): 46 | return "Line(%s, %s)" % (self.point, self.direction) 47 | 48 | 49 | def halfplane_optimize(lines, optimal_point): 50 | """Find the point closest to optimal_point in the intersection of the 51 | closed half-planes defined by lines which are in Hessian normal form 52 | (point-normal form).""" 53 | # We implement the quadratic time (though linear expected given randomly 54 | # permuted input) incremental half-plane intersection algorithm as laid 55 | # out in http://www.mpi-inf.mpg.de/~kavitha/lecture3.ps 56 | point = optimal_point 57 | for i, line in enumerate(lines): 58 | # If this half-plane already contains the current point, all is well. 59 | if dot(point - line.point, line.direction) >= 0: 60 | # assert False, point 61 | continue 62 | 63 | # Otherwise, the new optimum must lie on the newly added line. Compute 64 | # the feasible interval of the intersection of all the lines added so 65 | # far with the current one. 66 | prev_lines = itertools.islice(lines, i) 67 | left_dist, right_dist = line_halfplane_intersect(line, prev_lines) 68 | 69 | # Now project the optimal point onto the line segment defined by the 70 | # the above bounds. This gives us our new best point. 71 | point = point_line_project(line, optimal_point, left_dist, right_dist) 72 | return point 73 | 74 | def point_line_project(line, point, left_bound, right_bound): 75 | """Project point onto the line segment defined by line, which is in 76 | point-normal form, and the left and right bounds with respect to line's 77 | anchor point.""" 78 | # print("left_bound=%s, right_bound=%s" % (left_bound, right_bound)) 79 | new_dir = perp(line.direction) 80 | # print("new_dir=%s" % new_dir) 81 | proj_len = dot(point - line.point, new_dir) 82 | # print("proj_len=%s" % proj_len) 83 | clamped_len = clip(proj_len, left_bound, right_bound) 84 | # print("clamped_len=%s" % clamped_len) 85 | return line.point + new_dir * clamped_len 86 | 87 | def line_halfplane_intersect(line, other_lines): 88 | """Compute the signed offsets of the interval on the edge of the 89 | half-plane defined by line that is included in the half-planes defined by 90 | other_lines. 91 | 92 | The offsets are relative to line's anchor point, in units of line's 93 | direction. 94 | 95 | """ 96 | # We use the line intersection algorithm presented in 97 | # http://stackoverflow.com/a/565282/126977 to determine the intersection 98 | # point. "Left" is the negative of the canonical direction of the line. 99 | # "Right" is positive. 100 | left_dist = float("-inf") 101 | right_dist = float("inf") 102 | for prev_line in other_lines: 103 | num1 = dot(prev_line.direction, line.point - prev_line.point) 104 | den1 = det((line.direction, prev_line.direction)) 105 | # num2 = det((perp(prev_line.direction), line.point - prev_line.point)) 106 | # den2 = det((perp(line.direction), perp(prev_line.direction))) 107 | 108 | # assert abs(den1 - den2) < 1e-6, (den1, den2) 109 | # assert abs(num1 - num2) < 1e-6, (num1, num2) 110 | 111 | num = num1 112 | den = den1 113 | 114 | # Check for zero denominator, since ZeroDivisionError (or rather 115 | # FloatingPointError) won't necessarily be raised if using numpy. 116 | if den == 0: 117 | # The half-planes are parallel. 118 | if num < 0: 119 | # The intersection of the half-planes is empty; there is no 120 | # solution. 121 | raise InfeasibleError 122 | else: 123 | # The *half-planes* intersect, but their lines don't cross, so 124 | # ignore. 125 | continue 126 | 127 | # Signed offset of the point of intersection, relative to the line's 128 | # anchor point, in units of the line's direction. 129 | offset = num / den 130 | if den > 0: 131 | # Point of intersection is to the right. 132 | right_dist = min((right_dist, offset)) 133 | else: 134 | # Point of intersection is to the left. 135 | left_dist = max((left_dist, offset)) 136 | 137 | if left_dist > right_dist: 138 | # The interval is inconsistent, so the feasible region is empty. 139 | raise InfeasibleError 140 | return left_dist, right_dist 141 | 142 | def perp(a): 143 | return array((a[1], -a[0])) 144 | 145 | def norm_sq(x): 146 | return dot(x, x) 147 | 148 | def norm(x): 149 | return sqrt(norm_sq(x)) 150 | 151 | def normalized(x): 152 | l = norm_sq(x) 153 | assert l > 0, (x, l) 154 | return x / sqrt(l) 155 | 156 | if __name__ == '__main__': 157 | lines = [ 158 | Line((-2, 0), (-1, 1)), 159 | Line((0, -1), (1, 0)) 160 | ] 161 | point = array((1, 0)) 162 | result = halfplane_optimize(lines, point) 163 | print(result, norm(result)) 164 | 165 | # a = point_line_project(lines[0], point, -20, 20) 166 | # print((a - lines[0].point)/(-perp(lines[0].direction))) 167 | print(a) 168 | 169 | a = point_line_project(lines[1], point, -10000, -3) 170 | # print((a - lines[1].point)/(-perp(lines[1].direction))) 171 | print(a) 172 | -------------------------------------------------------------------------------- /orca/src/omni_controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import rospy 4 | import numpy 5 | from nav_msgs.msg import Odometry 6 | from geometry_msgs.msg import Twist 7 | from pyorca import Agent, orca, normalized 8 | 9 | N_AGENTS = 4 10 | RADIUS = 0.7 11 | tau = 5 12 | 13 | Ts = 0.1 14 | X = [(-5, 0.25), (5, 0), (0.00, 5), (-0.25, -5)] 15 | V = [(0, 0) for _ in xrange(len(X))] 16 | V_max = [1.0 for _ in xrange(len(X))] 17 | goal = [(5.0, 0.0), (-5.0, 0.0), (0.0, -5.0), (0.0, 5.0)] 18 | 19 | agents = [] 20 | 21 | for i in xrange(len(X)): 22 | vel = (numpy.array(goal[i]) - numpy.array(X[i])) 23 | if numpy.linalg.norm(vel) > V_max[i]: 24 | vel = normalized(vel) * V_max[i] 25 | agents.append(Agent(X[i], (0., 0.), RADIUS, V_max[i], vel)) 26 | 27 | def update_agents(agents): 28 | for i in xrange(len(X)): 29 | agents[i].pref_velocity = (numpy.array(goal[i]) - numpy.array(X[i])) 30 | if numpy.linalg.norm(agents[i].pref_velocity) > V_max[i]: 31 | agents[i].pref_velocity = normalized(agents[i].pref_velocity) * V_max[i] 32 | return agents 33 | 34 | def callback_0(msg): 35 | X[0] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 36 | def callback_1(msg): 37 | X[1] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 38 | def callback_2(msg): 39 | X[2] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 40 | def callback_3(msg): 41 | X[3] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 42 | 43 | 44 | rospy.init_node('omni_controller') 45 | 46 | sub_0 = rospy.Subscriber('/robot_0/odom', Odometry, callback_0) 47 | sub_1 = rospy.Subscriber('/robot_1/odom', Odometry, callback_1) 48 | sub_2 = rospy.Subscriber('/robot_2/odom', Odometry, callback_2) 49 | sub_3 = rospy.Subscriber('/robot_3/odom', Odometry, callback_3) 50 | pub = [] 51 | pub.append(rospy.Publisher('/robot_0/cmd_vel', Twist, queue_size=10)) 52 | pub.append(rospy.Publisher('/robot_1/cmd_vel', Twist, queue_size=10)) 53 | pub.append(rospy.Publisher('/robot_2/cmd_vel', Twist, queue_size=10)) 54 | pub.append(rospy.Publisher('/robot_3/cmd_vel', Twist, queue_size=10)) 55 | t = 0 56 | 57 | while not rospy.is_shutdown(): 58 | agents = update_agents(agents) 59 | for i, agent in enumerate(agents): 60 | candidates = agents[:i] + agents[i + 1:] 61 | agent.velocity, _ = orca(agent, candidates, tau, Ts) 62 | 63 | for i in xrange(len(X)): 64 | vel = Twist() 65 | vel.linear.x = agents[i].velocity[0] 66 | vel.linear.y = agents[i].velocity[1] 67 | 68 | pub[i].publish(vel) 69 | 70 | if t%100: 71 | print(X) 72 | #print(agents[0].velocity) 73 | t += 1 74 | rospy.sleep(Ts) 75 | -------------------------------------------------------------------------------- /orca/src/pyorca.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Mak Nazecic-Andrlon 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | 21 | """Implementation of the 2D ORCA algorithm as described by J. van der Berg, 22 | S. J. Guy, M. Lin and D. Manocha in 'Reciprocal n-body Collision Avoidance'.""" 23 | 24 | from __future__ import division 25 | 26 | import numpy 27 | from numpy import array, sqrt, copysign, dot 28 | from numpy.linalg import det 29 | 30 | from halfplaneintersect import halfplane_optimize, Line, perp, InfeasibleError 31 | 32 | # Method: 33 | # For each robot A and potentially colliding robot B, compute smallest change 34 | # in relative velocity 'u' that avoids collision. Find normal 'n' to VO at that 35 | # point. 36 | # For each such velocity 'u' and normal 'n', find half-plane as defined in (6). 37 | # Intersect half-planes and pick velocity closest to A's preferred velocity. 38 | 39 | class Agent(object): 40 | """A disk-shaped agent.""" 41 | def __init__(self, position, velocity, radius, max_speed, pref_velocity): 42 | super(Agent, self).__init__() 43 | self.position = array(position) 44 | self.velocity = array(velocity) 45 | self.radius = radius 46 | self.max_speed = max_speed 47 | self.pref_velocity = array(pref_velocity) 48 | 49 | 50 | def orca(agent, colliding_agents, t, dt): 51 | """Compute ORCA solution for agent. NOTE: velocity must be _instantly_ 52 | changed on tick *edge*, like first-order integration, otherwise the method 53 | undercompensates and you will still risk colliding.""" 54 | lines = [] 55 | for collider in colliding_agents: 56 | dv, n = get_avoidance_velocity(agent, collider, t, dt) 57 | line = Line(agent.velocity + dv / 2, n) 58 | lines.append(line) 59 | try: 60 | return halfplane_optimize(lines, agent.pref_velocity), lines 61 | except (InfeasibleError): 62 | print('Infeasible') 63 | return agent.velocity, lines 64 | 65 | def get_avoidance_velocity(agent, collider, t, dt): 66 | """Get the smallest relative change in velocity between agent and collider 67 | that will get them onto the boundary of each other's velocity obstacle 68 | (VO), and thus avert collision.""" 69 | 70 | # This is a summary of the explanation from the AVO paper. 71 | # 72 | # The set of all relative velocities that will cause a collision within 73 | # time tau is called the velocity obstacle (VO). If the relative velocity 74 | # is outside of the VO, no collision will happen for at least tau time. 75 | # 76 | # The VO for two moving disks is a circularly truncated triangle 77 | # (spherically truncated cone in 3D), with an imaginary apex at the 78 | # origin. It can be described by a union of disks: 79 | # 80 | # Define an open disk centered at p with radius r: 81 | # D(p, r) := {q | ||q - p|| < r} (1) 82 | # 83 | # Two disks will collide at time t iff ||x + vt|| < r, where x is the 84 | # displacement, v is the relative velocity, and r is the sum of their 85 | # radii. 86 | # 87 | # Divide by t: ||x/t + v|| < r/t, 88 | # Rearrange: ||v - (-x/t)|| < r/t. 89 | # 90 | # By (1), this is a disk D(-x/t, r/t), and it is the set of all velocities 91 | # that will cause a collision at time t. 92 | # 93 | # We can now define the VO for time tau as the union of all such disks 94 | # D(-x/t, r/t) for 0 < t <= tau. 95 | # 96 | # Note that the displacement and radius scale _inversely_ proportionally 97 | # to t, generating a line of disks of increasing radius starting at -x/t. 98 | # This is what gives the VO its cone shape. The _closest_ velocity disk is 99 | # at D(-x/tau, r/tau), and this truncates the VO. 100 | 101 | x = -(agent.position - collider.position) 102 | v = agent.velocity - collider.velocity 103 | r = agent.radius + collider.radius 104 | 105 | x_len_sq = norm_sq(x) 106 | 107 | if x_len_sq >= r * r: 108 | # We need to decide whether to project onto the disk truncating the VO 109 | # or onto the sides. 110 | # 111 | # The center of the truncating disk doesn't mark the line between 112 | # projecting onto the sides or the disk, since the sides are not 113 | # parallel to the displacement. We need to bring it a bit closer. How 114 | # much closer can be worked out by similar triangles. It works out 115 | # that the new point is at x/t cos(theta)^2, where theta is the angle 116 | # of the aperture (so sin^2(theta) = (r/||x||)^2). 117 | adjusted_center = x/t * (1 - (r*r)/x_len_sq) 118 | 119 | if dot(v - adjusted_center, adjusted_center) < 0: 120 | # v lies in the front part of the cone 121 | # print("front") 122 | # print("front", adjusted_center, x_len_sq, r, x, t) 123 | w = v - x/t 124 | u = normalized(w) * r/t - w 125 | n = normalized(w) 126 | else: # v lies in the rest of the cone 127 | # print("sides") 128 | # Rotate x in the direction of v, to make it a side of the cone. 129 | # Then project v onto that, and calculate the difference. 130 | leg_len = sqrt(x_len_sq - r*r) 131 | # The sign of the sine determines which side to project on. 132 | sine = copysign(r, det((v, x))) 133 | rot = array( 134 | ((leg_len, sine), 135 | (-sine, leg_len))) 136 | rotated_x = rot.dot(x) / x_len_sq 137 | n = perp(rotated_x) 138 | if sine < 0: 139 | # Need to flip the direction of the line to make the 140 | # half-plane point out of the cone. 141 | n = -n 142 | # print("rotated_x=%s" % rotated_x) 143 | u = rotated_x * dot(v, rotated_x) - v 144 | # print("u=%s" % u) 145 | else: 146 | # We're already intersecting. Pick the closest velocity to our 147 | # velocity that will get us out of the collision within the next 148 | # timestep. 149 | # print("intersecting") 150 | w = v - x/dt 151 | u = normalized(w) * r/dt - w 152 | n = normalized(w) 153 | return u, n 154 | 155 | def norm_sq(x): 156 | return dot(x, x) 157 | 158 | def normalized(x): 159 | l = norm_sq(x) 160 | assert l > 0, (x, l) 161 | return x / sqrt(l) 162 | 163 | def dist_sq(a, b): 164 | return norm_sq(b - a) 165 | -------------------------------------------------------------------------------- /orca/src/test.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Mak Nazecic-Andrlon 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a copy 4 | # of this software and associated documentation files (the "Software"), to deal 5 | # in the Software without restriction, including without limitation the rights 6 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | # copies of the Software, and to permit persons to whom the Software is 8 | # furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in all 11 | # copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | # SOFTWARE. 20 | 21 | 22 | from __future__ import division 23 | 24 | from pyorca import Agent, get_avoidance_velocity, orca, normalized, perp 25 | from numpy import array, rint, linspace, pi, cos, sin, linalg 26 | from numpy.linalg import norm 27 | import pygame 28 | 29 | import itertools 30 | import random 31 | 32 | N_AGENTS = 2 33 | RADIUS = 1.0 34 | 35 | agents = [] 36 | 37 | X = [(-15, 0), (15, 0.5), (0.00, 15), (-0.5, -15)] 38 | V = [(0.0, 0.0) for _ in xrange(len(X))] 39 | V_max = [5.0 for _ in xrange(len(X))] 40 | goal = [(15.0, 0.0), (-15.0, 0.0), (0.0, -15.0), (0.0, 15.0)] 41 | for i in range(N_AGENTS): 42 | #theta = 2 * pi * i / N_AGENTS 43 | #x = RADIUS * array((cos(theta), sin(theta))) #+ random.uniform(-1, 1) 44 | #vel = normalized(-x) * SPEED 45 | #pos = (random.uniform(-20, 20), random.uniform(-20, 20)) 46 | pos = X[i] 47 | vel = (array(goal[i]) - array(X[i])) 48 | if norm(vel) > V_max[i]: 49 | vel = normalized(vel) * V_max[i] 50 | agents.append(Agent(pos, (0., 0.), RADIUS, V_max[i], vel)) 51 | 52 | 53 | colors = [ 54 | (255, 0, 0), 55 | (0, 255, 0), 56 | (0, 0, 255), 57 | (255, 255, 0), 58 | (0, 255, 255), 59 | (255, 0, 255), 60 | ] 61 | 62 | pygame.init() 63 | 64 | dim = (640, 480) 65 | screen = pygame.display.set_mode(dim) 66 | 67 | O = array(dim) / 2 # Screen position of origin. 68 | scale = 6 # Drawing scale. 69 | 70 | clock = pygame.time.Clock() 71 | FPS = 10 72 | dt = 1/FPS 73 | tau = 5 74 | 75 | def draw_agent(agent, color): 76 | pygame.draw.circle(screen, color, rint(agent.position * scale + O).astype(int), int(round(agent.radius * scale)), 0) 77 | 78 | def draw_orca_circles(a, b): 79 | for x in linspace(0, tau, 21): 80 | if x == 0: 81 | continue 82 | pygame.draw.circle(screen, pygame.Color(0, 0, 255), rint((-(a.position - b.position) / x + a.position) * scale + O).astype(int), int(round((a.radius + b.radius) * scale / x)), 1) 83 | 84 | def draw_velocity(a): 85 | pygame.draw.line(screen, pygame.Color(0, 255, 255), rint(a.position * scale + O).astype(int), rint((a.position + a.velocity) * scale + O).astype(int), 1) 86 | # pygame.draw.line(screen, pygame.Color(255, 0, 255), rint(a.position * scale + O).astype(int), rint((a.position + a.pref_velocity) * scale + O).astype(int), 1) 87 | 88 | running = True 89 | accum = 0 90 | all_lines = [[]] * len(agents) 91 | while running: 92 | accum += clock.tick(FPS) 93 | 94 | while accum >= dt * 1000: 95 | accum -= dt * 1000 96 | 97 | new_vels = [None] * len(agents) 98 | for i, agent in enumerate(agents): 99 | candidates = agents[:i] + agents[i + 1:] 100 | # print(candidates) 101 | new_vels[i], all_lines[i] = orca(agent, candidates, tau, dt) 102 | # print(i, agent.velocity) 103 | 104 | for i, agent in enumerate(agents): 105 | agent.velocity = new_vels[i] 106 | agent.position = agent.position + agent.velocity * dt 107 | 108 | screen.fill(pygame.Color(0, 0, 0)) 109 | 110 | for agent in agents[1:]: 111 | draw_orca_circles(agents[0], agent) 112 | 113 | for agent, color in zip(agents, itertools.cycle(colors)): 114 | draw_agent(agent, color) 115 | draw_velocity(agent) 116 | # print(sqrt(norm_sq(agent.velocity))) 117 | 118 | for line in all_lines[0]: 119 | # Draw ORCA line 120 | alpha = agents[0].position + line.point + perp(line.direction) * 100 121 | beta = agents[0].position + line.point + perp(line.direction) * -100 122 | pygame.draw.line(screen, (255, 255, 255), rint(alpha * scale + O).astype(int), rint(beta * scale + O).astype(int), 1) 123 | 124 | # Draw normal to ORCA line 125 | gamma = agents[0].position + line.point 126 | delta = agents[0].position + line.point + line.direction 127 | pygame.draw.line(screen, (255, 255, 255), rint(gamma * scale + O).astype(int), rint(delta * scale + O).astype(int), 1) 128 | 129 | pygame.display.flip() 130 | 131 | ## updateAgents 132 | for i in range(N_AGENTS): 133 | agents[i].pref_velocity = (array(goal[i]) - array(agents[i].position)) 134 | if norm(agents[i].pref_velocity) > V_max[i]: 135 | agents[i].pref_velocity = normalized(agents[i].pref_velocity) * V_max[i] 136 | 137 | for event in pygame.event.get(): 138 | if event.type == pygame.QUIT: 139 | running = False 140 | pygame.quit() 141 | -------------------------------------------------------------------------------- /rvo/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.3) 2 | project(rvo) 3 | 4 | ## Compile as C++11, supported in ROS Kinetic and newer 5 | # add_compile_options(-std=c++11) 6 | 7 | ## Find catkin macros and libraries 8 | ## if COMPONENTS list like find_package(catkin REQUIRED COMPONENTS xyz) 9 | ## is used, also find other catkin packages 10 | find_package(catkin REQUIRED COMPONENTS 11 | rospy 12 | ) 13 | 14 | ## System dependencies are found with CMake's conventions 15 | # find_package(Boost REQUIRED COMPONENTS system) 16 | 17 | 18 | ## Uncomment this if the package has a setup.py. This macro ensures 19 | ## modules and global scripts declared therein get installed 20 | ## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html 21 | # catkin_python_setup() 22 | 23 | ################################################ 24 | ## Declare ROS messages, services and actions ## 25 | ################################################ 26 | 27 | ## To declare and build messages, services or actions from within this 28 | ## package, follow these steps: 29 | ## * Let MSG_DEP_SET be the set of packages whose message types you use in 30 | ## your messages/services/actions (e.g. std_msgs, actionlib_msgs, ...). 31 | ## * In the file package.xml: 32 | ## * add a build_depend tag for "message_generation" 33 | ## * add a build_depend and a exec_depend tag for each package in MSG_DEP_SET 34 | ## * If MSG_DEP_SET isn't empty the following dependency has been pulled in 35 | ## but can be declared for certainty nonetheless: 36 | ## * add a exec_depend tag for "message_runtime" 37 | ## * In this file (CMakeLists.txt): 38 | ## * add "message_generation" and every package in MSG_DEP_SET to 39 | ## find_package(catkin REQUIRED COMPONENTS ...) 40 | ## * add "message_runtime" and every package in MSG_DEP_SET to 41 | ## catkin_package(CATKIN_DEPENDS ...) 42 | ## * uncomment the add_*_files sections below as needed 43 | ## and list every .msg/.srv/.action file to be processed 44 | ## * uncomment the generate_messages entry below 45 | ## * add every package in MSG_DEP_SET to generate_messages(DEPENDENCIES ...) 46 | 47 | ## Generate messages in the 'msg' folder 48 | # add_message_files( 49 | # FILES 50 | # Message1.msg 51 | # Message2.msg 52 | # ) 53 | 54 | ## Generate services in the 'srv' folder 55 | # add_service_files( 56 | # FILES 57 | # Service1.srv 58 | # Service2.srv 59 | # ) 60 | 61 | ## Generate actions in the 'action' folder 62 | # add_action_files( 63 | # FILES 64 | # Action1.action 65 | # Action2.action 66 | # ) 67 | 68 | ## Generate added messages and services with any dependencies listed here 69 | # generate_messages( 70 | # DEPENDENCIES 71 | # std_msgs # Or other packages containing msgs 72 | # ) 73 | 74 | ################################################ 75 | ## Declare ROS dynamic reconfigure parameters ## 76 | ################################################ 77 | 78 | ## To declare and build dynamic reconfigure parameters within this 79 | ## package, follow these steps: 80 | ## * In the file package.xml: 81 | ## * add a build_depend and a exec_depend tag for "dynamic_reconfigure" 82 | ## * In this file (CMakeLists.txt): 83 | ## * add "dynamic_reconfigure" to 84 | ## find_package(catkin REQUIRED COMPONENTS ...) 85 | ## * uncomment the "generate_dynamic_reconfigure_options" section below 86 | ## and list every .cfg file to be processed 87 | 88 | ## Generate dynamic reconfigure parameters in the 'cfg' folder 89 | # generate_dynamic_reconfigure_options( 90 | # cfg/DynReconf1.cfg 91 | # cfg/DynReconf2.cfg 92 | # ) 93 | 94 | ################################### 95 | ## catkin specific configuration ## 96 | ################################### 97 | ## The catkin_package macro generates cmake config files for your package 98 | ## Declare things to be passed to dependent projects 99 | ## INCLUDE_DIRS: uncomment this if your package contains header files 100 | ## LIBRARIES: libraries you create in this project that dependent projects also need 101 | ## CATKIN_DEPENDS: catkin_packages dependent projects also need 102 | ## DEPENDS: system dependencies of this project that dependent projects also need 103 | catkin_package( 104 | # INCLUDE_DIRS include 105 | # LIBRARIES rvo 106 | # CATKIN_DEPENDS rospy 107 | # DEPENDS system_lib 108 | ) 109 | 110 | ########### 111 | ## Build ## 112 | ########### 113 | 114 | ## Specify additional locations of header files 115 | ## Your package locations should be listed before other locations 116 | include_directories( 117 | # include 118 | ${catkin_INCLUDE_DIRS} 119 | ) 120 | 121 | ## Declare a C++ library 122 | # add_library(${PROJECT_NAME} 123 | # src/${PROJECT_NAME}/rvo.cpp 124 | # ) 125 | 126 | ## Add cmake target dependencies of the library 127 | ## as an example, code may need to be generated before libraries 128 | ## either from message generation or dynamic reconfigure 129 | # add_dependencies(${PROJECT_NAME} ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) 130 | 131 | ## Declare a C++ executable 132 | ## With catkin_make all packages are built within a single CMake context 133 | ## The recommended prefix ensures that target names across packages don't collide 134 | # add_executable(${PROJECT_NAME}_node src/rvo_node.cpp) 135 | 136 | ## Rename C++ executable without prefix 137 | ## The above recommended prefix causes long target names, the following renames the 138 | ## target back to the shorter version for ease of user use 139 | ## e.g. "rosrun someones_pkg node" instead of "rosrun someones_pkg someones_pkg_node" 140 | # set_target_properties(${PROJECT_NAME}_node PROPERTIES OUTPUT_NAME node PREFIX "") 141 | 142 | ## Add cmake target dependencies of the executable 143 | ## same as for the library above 144 | # add_dependencies(${PROJECT_NAME}_node ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS}) 145 | 146 | ## Specify libraries to link a library or executable target against 147 | # target_link_libraries(${PROJECT_NAME}_node 148 | # ${catkin_LIBRARIES} 149 | # ) 150 | 151 | ############# 152 | ## Install ## 153 | ############# 154 | 155 | # all install targets should use catkin DESTINATION variables 156 | # See http://ros.org/doc/api/catkin/html/adv_user_guide/variables.html 157 | 158 | ## Mark executable scripts (Python etc.) for installation 159 | ## in contrast to setup.py, you can choose the destination 160 | # install(PROGRAMS 161 | # scripts/my_python_script 162 | # DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} 163 | # ) 164 | 165 | ## Mark executables and/or libraries for installation 166 | # install(TARGETS ${PROJECT_NAME} ${PROJECT_NAME}_node 167 | # ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} 168 | # LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION} 169 | # RUNTIME DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} 170 | # ) 171 | 172 | ## Mark cpp header files for installation 173 | # install(DIRECTORY include/${PROJECT_NAME}/ 174 | # DESTINATION ${CATKIN_PACKAGE_INCLUDE_DESTINATION} 175 | # FILES_MATCHING PATTERN "*.h" 176 | # PATTERN ".svn" EXCLUDE 177 | # ) 178 | 179 | ## Mark other files for installation (e.g. launch and bag files, etc.) 180 | # install(FILES 181 | # # myfile1 182 | # # myfile2 183 | # DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} 184 | # ) 185 | 186 | ############# 187 | ## Testing ## 188 | ############# 189 | 190 | ## Add gtest based cpp test target and link libraries 191 | # catkin_add_gtest(${PROJECT_NAME}-test test/test_rvo.cpp) 192 | # if(TARGET ${PROJECT_NAME}-test) 193 | # target_link_libraries(${PROJECT_NAME}-test ${PROJECT_NAME}) 194 | # endif() 195 | 196 | ## Add folders to be run by python nosetests 197 | # catkin_add_nosetests(test) 198 | -------------------------------------------------------------------------------- /rvo/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | rvo 4 | 0.0.0 5 | The rvo package 6 | 7 | 8 | 9 | 10 | glauberrleite 11 | 12 | 13 | 14 | 15 | 16 | TODO 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | catkin 52 | rospy 53 | rospy 54 | rospy 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /rvo/src/RVO.py: -------------------------------------------------------------------------------- 1 | from math import ceil, floor, sqrt 2 | import copy 3 | import numpy 4 | 5 | from math import cos, sin, tan, atan2, asin 6 | 7 | from math import pi as PI 8 | 9 | 10 | 11 | def distance(pose1, pose2): 12 | """ compute Euclidean distance for 2D """ 13 | return sqrt((pose1[0]-pose2[0])**2+(pose1[1]-pose2[1])**2)+0.001 14 | 15 | 16 | def RVO_update(X, V_des, V_current, ws_model): 17 | """ compute best velocity given the desired velocity, current velocity and workspace model""" 18 | ROB_RAD = ws_model['robot_radius']+0.1 19 | V_opt = list(V_current) 20 | for i in range(len(X)): 21 | vA = [V_current[i][0], V_current[i][1]] 22 | pA = [X[i][0], X[i][1]] 23 | RVO_BA_all = [] 24 | for j in range(len(X)): 25 | if i!=j: 26 | vB = [V_current[j][0], V_current[j][1]] 27 | pB = [X[j][0], X[j][1]] 28 | # use RVO 29 | transl_vB_vA = [pA[0]+0.5*(vB[0]+vA[0]), pA[1]+0.5*(vB[1]+vA[1])] 30 | # use VO 31 | #transl_vB_vA = [pA[0]+vB[0], pA[1]+vB[1]] 32 | dist_BA = distance(pA, pB) 33 | theta_BA = atan2(pB[1]-pA[1], pB[0]-pA[0]) 34 | if 2*ROB_RAD > dist_BA: 35 | dist_BA = 2*ROB_RAD 36 | theta_BAort = asin(2*ROB_RAD/dist_BA) 37 | theta_ort_left = theta_BA+theta_BAort 38 | bound_left = [cos(theta_ort_left), sin(theta_ort_left)] 39 | theta_ort_right = theta_BA-theta_BAort 40 | bound_right = [cos(theta_ort_right), sin(theta_ort_right)] 41 | # use HRVO 42 | # dist_dif = distance([0.5*(vB[0]-vA[0]),0.5*(vB[1]-vA[1])],[0,0]) 43 | # transl_vB_vA = [pA[0]+vB[0]+cos(theta_ort_left)*dist_dif, pA[1]+vB[1]+sin(theta_ort_left)*dist_dif] 44 | RVO_BA = [transl_vB_vA, bound_left, bound_right, dist_BA, 2*ROB_RAD] 45 | RVO_BA_all.append(RVO_BA) 46 | for hole in ws_model['circular_obstacles']: 47 | # hole = [x, y, rad] 48 | vB = [0, 0] 49 | pB = hole[0:2] 50 | transl_vB_vA = [pA[0]+vB[0], pA[1]+vB[1]] 51 | dist_BA = distance(pA, pB) 52 | theta_BA = atan2(pB[1]-pA[1], pB[0]-pA[0]) 53 | # over-approximation of square to circular 54 | OVER_APPROX_C2S = 1.5 55 | rad = hole[2]*OVER_APPROX_C2S 56 | if (rad+ROB_RAD) > dist_BA: 57 | dist_BA = rad+ROB_RAD 58 | theta_BAort = asin((rad+ROB_RAD)/dist_BA) 59 | theta_ort_left = theta_BA+theta_BAort 60 | bound_left = [cos(theta_ort_left), sin(theta_ort_left)] 61 | theta_ort_right = theta_BA-theta_BAort 62 | bound_right = [cos(theta_ort_right), sin(theta_ort_right)] 63 | RVO_BA = [transl_vB_vA, bound_left, bound_right, dist_BA, rad+ROB_RAD] 64 | RVO_BA_all.append(RVO_BA) 65 | vA_post = intersect(pA, V_des[i], RVO_BA_all) 66 | V_opt[i] = vA_post[:] 67 | return V_opt 68 | 69 | 70 | def intersect(pA, vA, RVO_BA_all): 71 | # print '----------------------------------------' 72 | # print 'Start intersection test' 73 | norm_v = distance(vA, [0, 0]) 74 | suitable_V = [] 75 | unsuitable_V = [] 76 | for theta in numpy.arange(0, 2*PI, 0.1): 77 | for rad in numpy.arange(0.02, norm_v+0.02, norm_v/5.0): 78 | new_v = [rad*cos(theta), rad*sin(theta)] 79 | suit = True 80 | for RVO_BA in RVO_BA_all: 81 | p_0 = RVO_BA[0] 82 | left = RVO_BA[1] 83 | right = RVO_BA[2] 84 | dif = [new_v[0]+pA[0]-p_0[0], new_v[1]+pA[1]-p_0[1]] 85 | theta_dif = atan2(dif[1], dif[0]) 86 | theta_right = atan2(right[1], right[0]) 87 | theta_left = atan2(left[1], left[0]) 88 | if in_between(theta_right, theta_dif, theta_left): 89 | suit = False 90 | break 91 | if suit: 92 | suitable_V.append(new_v) 93 | else: 94 | unsuitable_V.append(new_v) 95 | new_v = vA[:] 96 | suit = True 97 | for RVO_BA in RVO_BA_all: 98 | p_0 = RVO_BA[0] 99 | left = RVO_BA[1] 100 | right = RVO_BA[2] 101 | dif = [new_v[0]+pA[0]-p_0[0], new_v[1]+pA[1]-p_0[1]] 102 | theta_dif = atan2(dif[1], dif[0]) 103 | theta_right = atan2(right[1], right[0]) 104 | theta_left = atan2(left[1], left[0]) 105 | if in_between(theta_right, theta_dif, theta_left): 106 | suit = False 107 | break 108 | if suit: 109 | suitable_V.append(new_v) 110 | else: 111 | unsuitable_V.append(new_v) 112 | #---------------------- 113 | if suitable_V: 114 | # print 'Suitable found' 115 | vA_post = min(suitable_V, key = lambda v: distance(v, vA)) 116 | new_v = vA_post[:] 117 | for RVO_BA in RVO_BA_all: 118 | p_0 = RVO_BA[0] 119 | left = RVO_BA[1] 120 | right = RVO_BA[2] 121 | dif = [new_v[0]+pA[0]-p_0[0], new_v[1]+pA[1]-p_0[1]] 122 | theta_dif = atan2(dif[1], dif[0]) 123 | theta_right = atan2(right[1], right[0]) 124 | theta_left = atan2(left[1], left[0]) 125 | else: 126 | # print 'Suitable not found' 127 | tc_V = dict() 128 | for unsuit_v in unsuitable_V: 129 | tc_V[tuple(unsuit_v)] = 0 130 | tc = [] 131 | for RVO_BA in RVO_BA_all: 132 | p_0 = RVO_BA[0] 133 | left = RVO_BA[1] 134 | right = RVO_BA[2] 135 | dist = RVO_BA[3] 136 | rad = RVO_BA[4] 137 | dif = [unsuit_v[0]+pA[0]-p_0[0], unsuit_v[1]+pA[1]-p_0[1]] 138 | theta_dif = atan2(dif[1], dif[0]) 139 | theta_right = atan2(right[1], right[0]) 140 | theta_left = atan2(left[1], left[0]) 141 | if in_between(theta_right, theta_dif, theta_left): 142 | small_theta = abs(theta_dif-0.5*(theta_left+theta_right)) 143 | if abs(dist*sin(small_theta)) >= rad: 144 | rad = abs(dist*sin(small_theta)) 145 | big_theta = asin(abs(dist*sin(small_theta))/rad) 146 | dist_tg = abs(dist*cos(small_theta))-abs(rad*cos(big_theta)) 147 | if dist_tg < 0: 148 | dist_tg = 0 149 | tc_v = dist_tg/distance(dif, [0,0]) 150 | tc.append(tc_v) 151 | tc_V[tuple(unsuit_v)] = min(tc)+0.001 152 | WT = 0.2 153 | vA_post = min(unsuitable_V, key = lambda v: ((WT/tc_V[tuple(v)])+distance(v, vA))) 154 | return vA_post 155 | 156 | def in_between(theta_right, theta_dif, theta_left): 157 | if abs(theta_right - theta_left) <= PI: 158 | if theta_right <= theta_dif <= theta_left: 159 | return True 160 | else: 161 | return False 162 | else: 163 | if (theta_left <0) and (theta_right >0): 164 | theta_left += 2*PI 165 | if theta_dif < 0: 166 | theta_dif += 2*PI 167 | if theta_right <= theta_dif <= theta_left: 168 | return True 169 | else: 170 | return False 171 | if (theta_left >0) and (theta_right <0): 172 | theta_right += 2*PI 173 | if theta_dif < 0: 174 | theta_dif += 2*PI 175 | if theta_left <= theta_dif <= theta_right: 176 | return True 177 | else: 178 | return False 179 | 180 | def compute_V_des(X, goal, V_max): 181 | V_des = [] 182 | for i in xrange(len(X)): 183 | dif_x = [goal[i][k]-X[i][k] for k in xrange(2)] 184 | norm = distance(dif_x, [0, 0]) 185 | norm_dif_x = [dif_x[k]*V_max[k]/norm for k in xrange(2)] 186 | V_des.append(norm_dif_x[:]) 187 | if reach(X[i], goal[i], 0.1): 188 | V_des[i][0] = 0 189 | V_des[i][1] = 0 190 | return V_des 191 | 192 | def reach(p1, p2, bound=0.5): 193 | if distance(p1,p2)< bound: 194 | return True 195 | else: 196 | return False 197 | 198 | 199 | -------------------------------------------------------------------------------- /rvo/src/controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | # @author glauberrleite 4 | 5 | import rospy 6 | 7 | import numpy as np 8 | 9 | from RVO import RVO_update, reach, compute_V_des, reach 10 | from geometry_msgs.msg import Twist 11 | from gazebo_msgs.msg import ModelStates 12 | from kobuki_msgs.msg import MotorPower 13 | 14 | scenario = dict() 15 | scenario['robot_radius'] = 0.8 16 | scenario['circular_obstacles'] = [] 17 | scenario['boundary'] = [] 18 | 19 | Ts = 0.01 20 | X = [[0,0], [10,0]] 21 | orientation = [0, np.pi] 22 | V = [[0,0] for _ in xrange(len(X))] 23 | V_max = [1.0 for i in xrange(len(X))] 24 | goal = [[10, 0], [0,0]] 25 | 26 | def updateWorld(msg): 27 | X[1] = [float(msg.pose[1].position.x), float(msg.pose[1].position.y)] 28 | orientation[1] = 2* np.arctan2(float(msg.pose[1].orientation.z), float(msg.pose[1].orientation.w)) 29 | if np.abs(orientation[1]) > np.pi: 30 | orientation[1] = orientation[1] - np.sign(orientation[1]) * 2 * np.pi 31 | 32 | X[0] = [float(msg.pose[2].position.x), float(msg.pose[2].position.y)] 33 | orientation[0] = 2* np.arctan2(float(msg.pose[2].orientation.z), float(msg.pose[2].orientation.w)) 34 | if np.abs(orientation[0]) > np.pi: 35 | orientation[0] = orientation[0] - np.sign(orientation[0]) * 2 * np.pi 36 | 37 | 38 | rospy.init_node('rvo_controller') 39 | pub_vel_r0 = rospy.Publisher("robot0/mobile_base/commands/velocity", Twist, queue_size=10); 40 | pub_vel_r1 = rospy.Publisher("robot1/mobile_base/commands/velocity", Twist, queue_size=10); 41 | pub_mot_r0 = rospy.Publisher("robot0/mobile_base/commands/motor_power", MotorPower, queue_size=10); 42 | pub_mot_r1 = rospy.Publisher("robot1/mobile_base/commands/motor_power", MotorPower, queue_size=10); 43 | 44 | rospy.Subscriber('/gazebo/model_states', ModelStates, updateWorld) 45 | 46 | motor0 = MotorPower() 47 | motor1 = MotorPower() 48 | motor0.state = motor0.ON 49 | motor1.state = motor1.ON 50 | 51 | pub_mot_r0.publish(motor0) 52 | pub_mot_r1.publish(motor1) 53 | 54 | t = 0 55 | 56 | while not rospy.is_shutdown(): 57 | # compute desired vel to goal 58 | V_des = compute_V_des(X, goal, V_max) 59 | # compute the optimal vel to avoid collision 60 | V = RVO_update(X, V_des, V, scenario) 61 | 62 | # Compute ang_vel considering singularities 63 | angular_vel_0 = (np.arctan2(V[0][1], V[0][0]) - orientation[0]) 64 | if np.abs(angular_vel_0) > np.pi: 65 | angular_vel_0 = (orientation[0] - np.arctan2(V[0][1], V[0][0])) 66 | 67 | linear_vel_0 = np.sqrt(V[0][0]**2 + V[0][1]**2) 68 | vel_rob0 = Twist() 69 | vel_rob0.linear.x = linear_vel_0 70 | vel_rob0.linear.y = 0.0 71 | vel_rob0.linear.z = 0.0 72 | vel_rob0.angular.x = 0.0 73 | vel_rob0.angular.y = 0.0 74 | vel_rob0.angular.z = angular_vel_0 75 | 76 | angular_vel_1 = (np.arctan2(V[1][1], V[1][0]) - orientation[1]) 77 | if np.abs(angular_vel_1) > np.pi: 78 | angular_vel_1 = (orientation[0] - np.arctan2(V[1][1], V[1][0])) 79 | 80 | linear_vel_1 = np.sqrt(V[1][0]**2 + V[1][1]**2) 81 | vel_rob1 = Twist() 82 | vel_rob1.linear.x = linear_vel_1 83 | vel_rob1.linear.y = 0.0; 84 | vel_rob1.linear.z = 0.0; 85 | vel_rob1.angular.x = 0.0; 86 | vel_rob1.angular.y = 0.0; 87 | vel_rob1.angular.z = angular_vel_1 88 | 89 | pub_vel_r0.publish(vel_rob0) 90 | pub_vel_r1.publish(vel_rob1) 91 | 92 | #if t%100 == 0: 93 | # print(V_des[1]) 94 | # print(vel_rob1.linear.x) 95 | # print(vel_rob1.angular.z) 96 | # print(orientation) 97 | # print('---------') 98 | #t = t + 1 99 | 100 | rospy.sleep(Ts) 101 | -------------------------------------------------------------------------------- /rvo/src/dense_diff_controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import rospy 4 | import numpy as np 5 | from nav_msgs.msg import Odometry 6 | from rosgraph_msgs.msg import Clock 7 | from geometry_msgs.msg import Twist, Vector3 8 | from RVO import RVO_update, compute_V_des 9 | 10 | scenario = dict() 11 | scenario['robot_radius'] = 0.5 12 | scenario['circular_obstacles'] = [] 13 | scenario['boundary'] = [] 14 | 15 | Ts = 0.01 16 | X = [[-10., 0.], [10., 0.], [0.0, 10.], [0., -10.], [-10., 10.], [-10., -10.], [10.0, -10.], [10., 10.]] 17 | orientation = [0, np.pi, -np.pi/2, np.pi/2, -np.pi/4, np.pi/4, 3*np.pi/4, -3*np.pi/4] 18 | V = [[0., 0.] for _ in xrange(len(X))] 19 | V_min = [-1.0 for _ in xrange(len(X))] 20 | V_max = [1.0 for _ in xrange(len(X))] 21 | goal = [[10., 0.], [-10., 0.], [0.0, -10.], [0., 10.], [10., -10.], [10., 10.], [-10.0, 10.], [-10., -10.]] 22 | 23 | def velocityTransform(v, theta_0): 24 | angular = np.arctan2(v[1], v[0]) - theta_0 25 | linear = np.sqrt(v[0]**2 + v[1]**2) 26 | 27 | # Handling singularity 28 | if np.abs(angular) > np.pi: 29 | angular -= np.sign(angular) * 2 * np.pi 30 | 31 | return [linear, angular] 32 | 33 | def callback_0(msg): 34 | X[0] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 35 | orientation[0] = 2 * np.arctan2(float(msg.pose.pose.orientation.z), float(msg.pose.pose.orientation.w)) 36 | def callback_1(msg): 37 | X[1] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 38 | orientation[1] = 2 * np.arctan2(float(msg.pose.pose.orientation.z), float(msg.pose.pose.orientation.w)) 39 | def callback_2(msg): 40 | X[2] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 41 | orientation[2] = 2 * np.arctan2(float(msg.pose.pose.orientation.z), float(msg.pose.pose.orientation.w)) 42 | def callback_3(msg): 43 | X[3] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 44 | orientation[3] = 2 * np.arctan2(float(msg.pose.pose.orientation.z), float(msg.pose.pose.orientation.w)) 45 | def callback_4(msg): 46 | X[4] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 47 | orientation[4] = 2 * np.arctan2(float(msg.pose.pose.orientation.z), float(msg.pose.pose.orientation.w)) 48 | def callback_5(msg): 49 | X[5] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 50 | orientation[5] = 2 * np.arctan2(float(msg.pose.pose.orientation.z), float(msg.pose.pose.orientation.w)) 51 | def callback_6(msg): 52 | X[6] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 53 | orientation[6] = 2 * np.arctan2(float(msg.pose.pose.orientation.z), float(msg.pose.pose.orientation.w)) 54 | def callback_7(msg): 55 | X[7] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 56 | orientation[7] = 2 * np.arctan2(float(msg.pose.pose.orientation.z), float(msg.pose.pose.orientation.w)) 57 | 58 | 59 | rospy.init_node('diff_controller') 60 | 61 | sub_0 = rospy.Subscriber('/robot_0/odom', Odometry, callback_0) 62 | sub_1 = rospy.Subscriber('/robot_1/odom', Odometry, callback_1) 63 | sub_2 = rospy.Subscriber('/robot_2/odom', Odometry, callback_2) 64 | sub_3 = rospy.Subscriber('/robot_3/odom', Odometry, callback_3) 65 | sub_4 = rospy.Subscriber('/robot_4/odom', Odometry, callback_4) 66 | sub_5 = rospy.Subscriber('/robot_5/odom', Odometry, callback_5) 67 | sub_6 = rospy.Subscriber('/robot_6/odom', Odometry, callback_6) 68 | sub_7 = rospy.Subscriber('/robot_7/odom', Odometry, callback_7) 69 | pub = [] 70 | pub.append(rospy.Publisher('/robot_0/cmd_vel', Twist, queue_size=10)) 71 | pub.append(rospy.Publisher('/robot_1/cmd_vel', Twist, queue_size=10)) 72 | pub.append(rospy.Publisher('/robot_2/cmd_vel', Twist, queue_size=10)) 73 | pub.append(rospy.Publisher('/robot_3/cmd_vel', Twist, queue_size=10)) 74 | pub.append(rospy.Publisher('/robot_4/cmd_vel', Twist, queue_size=10)) 75 | pub.append(rospy.Publisher('/robot_5/cmd_vel', Twist, queue_size=10)) 76 | pub.append(rospy.Publisher('/robot_6/cmd_vel', Twist, queue_size=10)) 77 | pub.append(rospy.Publisher('/robot_7/cmd_vel', Twist, queue_size=10)) 78 | pub_setpoint = rospy.Publisher('/setpoint', Vector3, queue_size=10) 79 | 80 | setpoint = Vector3() 81 | setpoint.x = goal[4][0] 82 | setpoint.y = goal[4][1] 83 | 84 | rospy.wait_for_message('/clock', Clock) 85 | while not rospy.is_shutdown(): 86 | 87 | V_des = compute_V_des(X, goal, V_max) 88 | V = RVO_update(X, V_des, V, scenario) 89 | 90 | 91 | for i in xrange(len(X)): 92 | vel = Twist() 93 | [vel.linear.x, vel.angular.z] = velocityTransform(V[i], orientation[i]) 94 | 95 | pub[i].publish(vel) 96 | 97 | pub_setpoint.publish(setpoint) 98 | rospy.sleep(Ts) 99 | -------------------------------------------------------------------------------- /rvo/src/dense_omni_controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import rospy 4 | import sys 5 | import numpy 6 | from nav_msgs.msg import Odometry 7 | from geometry_msgs.msg import Twist, Vector3 8 | from RVO import RVO_update, compute_V_des 9 | from rosgraph_msgs.msg import Clock 10 | 11 | scenario = dict() 12 | scenario['robot_radius'] = 0.5 13 | scenario['circular_obstacles'] = [] 14 | scenario['boundary'] = [] 15 | 16 | Ts = 0.01 17 | X = [[-10., 0.], [10., 0.], [0.0, 10.], [0., -10.], [-10., 10.], [-10., -10.], [10.0, -10.], [10., 10.]] 18 | V = [[0., 0.] for _ in xrange(len(X))] 19 | V_min = [-1.0 for _ in xrange(len(X))] 20 | V_max = [1.0 for _ in xrange(len(X))] 21 | goal = [[10., 0.], [-10., 0.], [0.0, -10.], [0., 10.], [10., -10.], [10., 10.], [-10.0, 10.], [-10., -10.]] 22 | 23 | def callback_0(msg): 24 | X[0] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 25 | def callback_1(msg): 26 | X[1] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 27 | def callback_2(msg): 28 | X[2] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 29 | def callback_3(msg): 30 | X[3] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 31 | def callback_4(msg): 32 | X[4] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 33 | def callback_5(msg): 34 | X[5] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 35 | def callback_6(msg): 36 | X[6] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 37 | def callback_7(msg): 38 | X[7] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 39 | 40 | 41 | rospy.init_node('omni_controller') 42 | 43 | sub_0 = rospy.Subscriber('/robot_0/odom', Odometry, callback_0) 44 | sub_1 = rospy.Subscriber('/robot_1/odom', Odometry, callback_1) 45 | sub_2 = rospy.Subscriber('/robot_2/odom', Odometry, callback_2) 46 | sub_3 = rospy.Subscriber('/robot_3/odom', Odometry, callback_3) 47 | sub_4 = rospy.Subscriber('/robot_4/odom', Odometry, callback_4) 48 | sub_5 = rospy.Subscriber('/robot_5/odom', Odometry, callback_5) 49 | sub_6 = rospy.Subscriber('/robot_6/odom', Odometry, callback_6) 50 | sub_7 = rospy.Subscriber('/robot_7/odom', Odometry, callback_7) 51 | pub = [] 52 | pub.append(rospy.Publisher('/robot_0/cmd_vel', Twist, queue_size=10)) 53 | pub.append(rospy.Publisher('/robot_1/cmd_vel', Twist, queue_size=10)) 54 | pub.append(rospy.Publisher('/robot_2/cmd_vel', Twist, queue_size=10)) 55 | pub.append(rospy.Publisher('/robot_3/cmd_vel', Twist, queue_size=10)) 56 | pub.append(rospy.Publisher('/robot_4/cmd_vel', Twist, queue_size=10)) 57 | pub.append(rospy.Publisher('/robot_5/cmd_vel', Twist, queue_size=10)) 58 | pub.append(rospy.Publisher('/robot_6/cmd_vel', Twist, queue_size=10)) 59 | pub.append(rospy.Publisher('/robot_7/cmd_vel', Twist, queue_size=10)) 60 | pub_setpoint = rospy.Publisher('/setpoint', Vector3, queue_size=10) 61 | 62 | setpoint = Vector3() 63 | setpoint.x = goal[4][0] 64 | setpoint.y = goal[4][1] 65 | 66 | rospy.wait_for_message('/clock', Clock) 67 | while not rospy.is_shutdown(): 68 | 69 | V_des = compute_V_des(X, goal, V_max) 70 | V = RVO_update(X, V_des, V, scenario) 71 | 72 | for i in xrange(len(X)): 73 | vel = Twist() 74 | vel.linear.x = V[i][0] 75 | vel.linear.y = V[i][1] 76 | 77 | pub[i].publish(vel) 78 | 79 | pub_setpoint.publish(setpoint) 80 | rospy.sleep(Ts) 81 | -------------------------------------------------------------------------------- /rvo/src/diff_omni_controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import rospy 4 | import numpy as np 5 | from nav_msgs.msg import Odometry 6 | from geometry_msgs.msg import Twist 7 | from RVO import RVO_update, compute_V_des 8 | 9 | scenario = dict() 10 | scenario['robot_radius'] = 0.4 11 | scenario['circular_obstacles'] = [] 12 | scenario['boundary'] = [] 13 | 14 | Ts = 0.01 15 | X = [[-5,0], [5,0], [0, 5], [0, -5]] 16 | V = [[0,0] for _ in xrange(len(X))] 17 | orientation = [0, np.pi, -np.pi/2, np.pi/2] 18 | V_max = [1.0, 1.0, 1.0, 1.0] 19 | goal = [[5, 0], [-5,0], [0, -5], [0, 5]] 20 | 21 | def velocityTransform(v, theta_0): 22 | angular = np.arctan2(v[1], v[0]) - theta_0 23 | linear = np.sqrt(v[0]**2 + v[1]**2) 24 | 25 | # Handling singularity 26 | if np.abs(angular) > np.pi: 27 | angular -= np.sign(angular) * 2 * np.pi 28 | 29 | return [linear, angular] 30 | 31 | def callback_0(msg): 32 | X[0] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 33 | orientation[0] = 2 * np.arctan2(float(msg.pose.pose.orientation.z), float(msg.pose.pose.orientation.w)) 34 | def callback_1(msg): 35 | X[1] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 36 | orientation[1] = 2 * np.arctan2(float(msg.pose.pose.orientation.z), float(msg.pose.pose.orientation.w)) 37 | def callback_2(msg): 38 | X[2] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 39 | orientation[2] = 2 * np.arctan2(float(msg.pose.pose.orientation.z), float(msg.pose.pose.orientation.w)) 40 | def callback_3(msg): 41 | X[3] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 42 | orientation[3] = 2 * np.arctan2(float(msg.pose.pose.orientation.z), float(msg.pose.pose.orientation.w)) 43 | 44 | 45 | rospy.init_node('omni_controller') 46 | 47 | sub_0 = rospy.Subscriber('/robot_0/odom', Odometry, callback_0) 48 | sub_1 = rospy.Subscriber('/robot_1/odom', Odometry, callback_1) 49 | sub_2 = rospy.Subscriber('/robot_2/odom', Odometry, callback_2) 50 | sub_3 = rospy.Subscriber('/robot_3/odom', Odometry, callback_3) 51 | pub_0 = rospy.Publisher('/robot_0/cmd_vel', Twist, queue_size=10) 52 | pub_1 = rospy.Publisher('/robot_1/cmd_vel', Twist, queue_size=10) 53 | pub_2 = rospy.Publisher('/robot_2/cmd_vel', Twist, queue_size=10) 54 | pub_3 = rospy.Publisher('/robot_3/cmd_vel', Twist, queue_size=10) 55 | t = 0 56 | while not rospy.is_shutdown(): 57 | 58 | V_des = compute_V_des(X, goal, V_max) 59 | V = RVO_update(X, V_des, V, scenario) 60 | 61 | vel_0 = Twist() 62 | 63 | [linear_vel_0, angular_vel_0] = velocityTransform(V[0], orientation[0]) 64 | 65 | vel_0.linear.x = linear_vel_0 66 | vel_0.angular.z = angular_vel_0 67 | 68 | vel_1 = Twist() 69 | 70 | [linear_vel_1, angular_vel_1] = velocityTransform(V[1], orientation[1]) 71 | 72 | vel_1.linear.x = linear_vel_1 73 | vel_1.angular.z = angular_vel_1 74 | 75 | vel_2 = Twist() 76 | 77 | [linear_vel_2, angular_vel_2] = velocityTransform(V[2], orientation[2]) 78 | 79 | vel_2.linear.x = linear_vel_2 80 | vel_2.angular.z = angular_vel_2 81 | 82 | vel_3 = Twist() 83 | 84 | [linear_vel_3, angular_vel_3] = velocityTransform(V[3], orientation[3]) 85 | 86 | vel_3.linear.x = linear_vel_3 87 | vel_3.angular.z = angular_vel_3 88 | 89 | 90 | pub_0.publish(vel_0) 91 | pub_1.publish(vel_1) 92 | pub_2.publish(vel_2) 93 | pub_3.publish(vel_3) 94 | 95 | 96 | if t%100: 97 | print(X) 98 | t += 1 99 | rospy.sleep(Ts) 100 | -------------------------------------------------------------------------------- /rvo/src/omni_controller.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import rospy 4 | import sys 5 | import numpy 6 | from nav_msgs.msg import Odometry 7 | from geometry_msgs.msg import Twist 8 | from RVO import RVO_update, compute_V_des 9 | 10 | scenario = dict() 11 | scenario['robot_radius'] = 0.4 12 | scenario['circular_obstacles'] = [] 13 | scenario['boundary'] = [] 14 | 15 | Ts = 0.01 16 | X = [[-5., 0.], [5., 0.], [0.0, 5.], [0., -5.]] 17 | V = [[0., 0.] for _ in xrange(len(X))] 18 | V_max = [1.0 for _ in xrange(len(X))] 19 | goal = [[5., 0.], [-5., 0.], [0.0, -5.], [0., 5.]] 20 | 21 | def callback_0(msg): 22 | X[0] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 23 | def callback_1(msg): 24 | X[1] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 25 | def callback_2(msg): 26 | X[2] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 27 | def callback_3(msg): 28 | X[3] = (float(msg.pose.pose.position.x), float(msg.pose.pose.position.y)) 29 | 30 | rospy.init_node('omni_controller') 31 | 32 | sub_0 = rospy.Subscriber('/robot_0/odom', Odometry, callback_0) 33 | sub_1 = rospy.Subscriber('/robot_1/odom', Odometry, callback_1) 34 | sub_2 = rospy.Subscriber('/robot_2/odom', Odometry, callback_2) 35 | sub_3 = rospy.Subscriber('/robot_3/odom', Odometry, callback_3) 36 | pub = [] 37 | pub.append(rospy.Publisher('/robot_0/cmd_vel', Twist, queue_size=10)) 38 | pub.append(rospy.Publisher('/robot_1/cmd_vel', Twist, queue_size=10)) 39 | pub.append(rospy.Publisher('/robot_2/cmd_vel', Twist, queue_size=10)) 40 | pub.append(rospy.Publisher('/robot_3/cmd_vel', Twist, queue_size=10)) 41 | t = 0 42 | while not rospy.is_shutdown(): 43 | 44 | V_des = compute_V_des(X, goal, V_max) 45 | V = RVO_update(X, V_des, V, scenario) 46 | 47 | for i in xrange(len(X)): 48 | vel = Twist() 49 | vel.linear.x = V[i][0] 50 | vel.linear.y = V[i][1] 51 | 52 | pub[i].publish(vel) 53 | 54 | 55 | if t%100: 56 | print(X) 57 | print('--------------') 58 | t += 1 59 | rospy.sleep(Ts) 60 | -------------------------------------------------------------------------------- /stage_worlds/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/glauberrleite/navigation-collision-avoidance/de134f59db128f99f89e823e04a89f30a2701fbf/stage_worlds/circle.png -------------------------------------------------------------------------------- /stage_worlds/dense_diff.world: -------------------------------------------------------------------------------- 1 | define robot position ( 2 | origin [0 0 0 0] 3 | drive "diff" 4 | bitmap "circle.png" 5 | ) 6 | 7 | 8 | #resolution 0.02 9 | 10 | #interval_sim 100 11 | 12 | #window ( 13 | # size [745 448] 14 | # rotate [0 0] 15 | # scale 18.806 16 | #) 17 | 18 | robot ( 19 | pose [ -10 0 0 0] 20 | name "r0" 21 | color "blue" 22 | ) 23 | 24 | robot ( 25 | pose [ 10 0 0 180] 26 | name "r1" 27 | color "red" 28 | ) 29 | 30 | robot ( 31 | pose [ 0 10 0 270] 32 | name "r2" 33 | color "black" 34 | ) 35 | 36 | robot ( 37 | pose [0 -10 0 90] 38 | name "r3" 39 | color "pink" 40 | ) 41 | 42 | robot ( 43 | pose [-10 10 0 315] 44 | name "r4" 45 | color "yellow" 46 | ) 47 | 48 | robot ( 49 | pose [-10 -10 0 45] 50 | name "r5" 51 | color "green" 52 | ) 53 | 54 | robot ( 55 | pose [10 -10 0 135] 56 | name "r6" 57 | color "gray" 58 | ) 59 | 60 | robot ( 61 | pose [10 10 0 225] 62 | name "r7" 63 | color "orange" 64 | ) -------------------------------------------------------------------------------- /stage_worlds/dense_omni.world: -------------------------------------------------------------------------------- 1 | define robot position ( 2 | origin [0.000 0.000 0.000 0.000] 3 | drive "omni" 4 | bitmap "circle.png" 5 | ) 6 | 7 | 8 | #resolution 0.02 9 | 10 | #interval_sim 100 11 | 12 | #window ( 13 | # size [745 448] 14 | # rotate [0 0] 15 | # scale 18.806 16 | #) 17 | 18 | robot ( 19 | pose [ -10.000 0.000 0.000 0.000] 20 | name "r0" 21 | color "blue" 22 | ) 23 | 24 | robot ( 25 | pose [ 10.000 0.000 0.000 0.000] 26 | name "r1" 27 | color "red" 28 | ) 29 | 30 | robot ( 31 | pose [ 0.000 10.000 0.000 0.000] 32 | name "r2" 33 | color "black" 34 | ) 35 | 36 | robot ( 37 | pose [0.000 -10.000 0.000 0.000] 38 | name "r3" 39 | color "pink" 40 | ) 41 | 42 | robot ( 43 | pose [-10.000 10.000 0.000 0.000] 44 | name "r4" 45 | color "yellow" 46 | ) 47 | 48 | robot ( 49 | pose [-10.000 -10.000 0.000 0.000] 50 | name "r5" 51 | color "green" 52 | ) 53 | 54 | robot ( 55 | pose [10.000 -10.000 0.000 0.000] 56 | name "r6" 57 | color "gray" 58 | ) 59 | 60 | robot ( 61 | pose [10.000 10.000 0.000 0.000] 62 | name "r7" 63 | color "orange" 64 | ) -------------------------------------------------------------------------------- /stage_worlds/world1.world: -------------------------------------------------------------------------------- 1 | define robot position ( 2 | origin [0 0 0 0] 3 | drive "omni" 4 | bitmap "circle.png" 5 | ) 6 | 7 | 8 | #resolution 0.02 9 | 10 | #interval_sim 100 11 | 12 | #window ( 13 | # size [745 448] 14 | # rotate [0 0] 15 | # scale 18.806 16 | #) 17 | 18 | robot ( 19 | pose [ -5 0 0 0] 20 | name "r0" 21 | color "blue" 22 | ) 23 | 24 | robot ( 25 | pose [ 5 0 0 0] 26 | name "r1" 27 | color "red" 28 | ) 29 | 30 | robot ( 31 | pose [ 0 5 0 0] 32 | name "r2" 33 | color "black" 34 | ) 35 | 36 | robot ( 37 | pose [0 -5 0 0] 38 | name "r3" 39 | color "pink" 40 | ) 41 | -------------------------------------------------------------------------------- /stage_worlds/world2.world: -------------------------------------------------------------------------------- 1 | define robot position ( 2 | origin [0 0 0 0] 3 | drive "diff" 4 | bitmap "circle.png" 5 | ) 6 | 7 | 8 | #resolution 0.02 9 | 10 | #interval_sim 100 11 | 12 | #window ( 13 | # size [745 448] 14 | # rotate [0 0] 15 | # scale 18.806 16 | #) 17 | 18 | robot ( 19 | pose [ -5 0 0 0] 20 | name "r0" 21 | color "blue" 22 | ) 23 | 24 | robot ( 25 | pose [ 5 0 0 180] 26 | name "r1" 27 | color "red" 28 | ) 29 | 30 | robot ( 31 | pose [ 0 5 0 270] 32 | name "r2" 33 | color "black" 34 | ) 35 | 36 | robot ( 37 | pose [0 -5 0 90] 38 | name "r3" 39 | color "pink" 40 | ) 41 | -------------------------------------------------------------------------------- /stage_worlds/world_test.world: -------------------------------------------------------------------------------- 1 | define robot position ( 2 | origin [0 0 0 0] 3 | drive "omni" 4 | bitmap "circle.png" 5 | ) 6 | 7 | 8 | #resolution 0.02 9 | 10 | #interval_sim 100 11 | 12 | #window ( 13 | # size [745 448] 14 | # rotate [0 0] 15 | # scale 18.806 16 | #) 17 | 18 | robot ( 19 | pose [ -5 0 0 0] 20 | name "r0" 21 | color "blue" 22 | ) --------------------------------------------------------------------------------