diff --git a/README.md b/README.md
index 7a576ca..c0b400c 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,509 @@
Dynamic Window Approach
===================================
-### License
+2D Dynamic Window Approach [[1]](#ref1) Motion Planning algorithm written in C with Python Bindings.
+
+![Python Demo](https://raw.githubusercontent.com/goktug97/DynamicWindowApproach/master/dwa.gif)
+
+
+**Table of Contents**
+
+- [Dynamic Window Approach](#dynamic-window-approach)
+ - [Requirements](#requirements)
+ - [Python](#python)
+ - [C Libraries (Optional for the Demo)](#c-libraries-optional-for-the-demo)
+ - [Installation](#installation)
+ - [Compile and Install C Library](#compile-and-install-c-library)
+ - [Install Python Bindings](#install-python-bindings)
+ - [PyPI](#pypi)
+ - [Source](#source)
+ - [Usage](#usage)
+ - [Documentation](#documentation)
+ - [Structs and Classes](#structs-and-classes)
+ - [Rect](#rect)
+ - [Config](#config)
+ - [Velocity](#velocity)
+ - [Point](#point)
+ - [PointCloud](#pointcloud)
+ - [Pose](#pose)
+ - [DynamicWindow](#dynamicwindow)
+ - [Functions](#functions)
+ - [planning](#planning)
+ - [createDynamicWindow](#createdynamicwindow)
+ - [freeDynamicWindow](#freedynamicwindow)
+ - [motion](#motion)
+ - [calculateVelocityCost](#calculatevelocitycost)
+ - [calculateHeadingCost](#calculateheadingcost)
+ - [calculateClearanceCost](#calculateclearancecost)
+ - [createPointCloud](#createpointcloud)
+ - [freePointCloud](#freepointcloud)
+ - [References](#references)
+ - [License](#license)
+
+
+
+## Requirements
+
+### Python
+
+* Python >= 3.6
+* cython
+* numpy
+* cv2 (Optional for the demo)
+
+### C Libraries (Optional for the Demo)
+
+* SDL
+* OpenGL
+
+## Installation
+
+You can directly install Python bindings without compiling the library.
+
+### Compile and Install C Library
+
+```bash
+git clone https://github.com/goktug97/DynamicWindowApproach
+cd DynamicWindowApproach
+mkdir build
+cd build
+cmake ..
+make
+make install
+# Optional: Build Demo
+make demo
+```
+
+### Install Python Bindings
+
+#### PyPI
+
+```bash
+pip3 install dynamic-window-approach --user
+```
+
+#### Source
+
+```bash
+git clone https://github.com/goktug97/DynamicWindowApproach
+cd DynamicWindowApproach
+python3 setup.py install --user
+```
+
+## Usage
+
+- Only function you need to run to plan the next move is the
+[planning](#planning) function. Rest of the code for both C and
+Python examples are just to create simulation environment and GUI.
+The 2 examples that you can find in
+[examples](https://github.com/goktug97/DynamicWindowApproach/blob/master/examples/)
+folder is the same demo but implemented using different libraries for
+visualization.
+
+- The Python example uses OpenCV and you can run it by executing `python3 demo.py`
+in the examples folder.
+
+- The C example uses OpenGL and SDL and you can run it by executing `./demo` in
+bin folder. The bin folder is created after the compile so if you didn't
+compile the demo while installing the library. Go to `build` directory and run
+`make demo`.
+
+- There is also a web application demo in
+a different repository which can be find at [DWA Web
+Demo](https://github.com/goktug97/DWAWebDemo).
+
+## Documentation
+
+While the [planning](#planning) function is the only function that a user needs
+to call for the planning, all of the functions are exposed to the user
+for both C and Python for no reasons.
+
+### Structs and Classes
+If you are using Python bindings, you don't need to use any of these
+classes except [Config](#config). The functions accept built-in or
+numpy types. The functions create required classes inside for easy
+usage. For example a snippet from the [planning](#planning) function;
+
+``` cython
+cdef float x, y, yaw, v , w, gx, gy
+cdef PointCloud _point_cloud = PointCloud(point_cloud)
+x, y, yaw = pose
+v, w = velocity
+gx, gy = goal
+cdef Pose _pose = Pose(Point(x, y), yaw)
+cdef Velocity _velocity = Velocity(v, w)
+cdef Point _goal = Point(gx, gy)
+```
+
+- Structs are for C
+- Classes are for Python
+
+#### Rect
+
+- *struct* Rect
+
+ * Given center of the robot is (0, 0)
+ * **Parameters**:
+ - **xmin** - floating-point minimum x-coordinate of the robot.
+ - **ymin** - floating-point minimum y-coordinate of the robot.
+ - **xmax** - floating-point maximum x-coordinate of the robot.
+ - **ymax** - floating-point maximum y-coordinate of the robot.
+
+#### Config
+
+- *struct* Config
+
+ * **Parameters**:
+ - **maxSpeed** - floating-point maximum linear speed robot can reach [m/s]
+ - **minSpeed** - floating-point minimum linea speed robot can fall [m/s]
+ - **maxYawrate** - floating-point maximum angular spped robot can reach [yaw/s]
+ - **maxAccel** - floating-point maximum linear acceleration robot can reach [m/ss]
+ - **maxdYawrate** - floating-point maximum angular acceleration robot can reach [yaw/ss]
+ - **velocityResolution** - floating-point linear speed resolution [m/s]
+ - **yawrateResolution** - floating-point angular speed resolution [m/s]
+ - **dt** - floating-point time change [s]
+ - **predictTime** - floating-point simulation time [s]
+ - **heading** - floating-point heading cost weight
+ - **clearance** - floating-point clearance cost weight
+ - **velocity** - floating-point velocity cost weight
+ - **base** - [Rect](#rect)
+
+- *class* Config
+
+ ``` python
+ Config(float max_speed, float min_speed,
+ float max_yawrate, float max_accel, float max_dyawrate,
+ float velocity_resolution, float yawrate_resolution, float dt,
+ float predict_time, float heading, float clearance, float velocity,
+ list base)
+ ```
+
+#### Velocity
+
+- *struct* Velocity
+
+ * **Parameters**:
+ - **linearVelocity** - floating-point linear velocity of the robot [m/s]
+ - **angularVelocity** - floating-point angular velocity of the robot [yaw/s]
+
+- *class* Velocity
+
+ ``` python
+ Velocity(float linear_velocity, float angular_velocity)
+ ```
+
+#### Point
+
+- *struct* Point
+
+ * **Parameters**:
+ - **x** – floating-point x-coordinate of the point.
+ - **y** – floating-point y-coordinate of the point.
+
+- *class* Point
+
+ ``` python
+ Point(float x, float y)
+ ```
+
+#### PointCloud
+
+- *struct* PointCloud
+
+ * int **size**
+ - Number of points.
+ * Point ***points**
+ - Array of [points](#point).
+
+- *class* PointCloud
+
+ ``` python
+ PointCloud(np.ndarray[np.float32_t, ndim=2] point_cloud)
+ ```
+
+#### Pose
+
+- *struct* Pose
+ * Point **point**
+ - Coordinate of the robot.
+ * float **yaw**
+ - Angle of the robot.
+
+- *class* Pose
+
+ ``` python
+ Pose(Point point, float yaw)
+ ```
+
+#### DynamicWindow
+
+- *struct* DynamicWindow
+ * int **nPossibleV**:
+ - Number of linear velocities in the Dynamic Window.
+ * float ***possibleV**:
+ - Array of linear velocities with resolution of [Config.velocityResolution](#config)
+ * int **nPossibleW**:
+ - Number of angular velocities in the Dynamic Window
+ * float ***possibleW**:
+ - Array of angular velocities with resolution of [Config.yawrateResolution](#config)
+
+- *class* DynamicWindow
+
+ ```python
+ DynamicWindow(tuple velocity, Config config)
+ ```
+
+### Functions
+
+#### planning
+
+Calculates best linear and angular velocities given the state. Only
+required function to use this library.
+
+- C
+
+ ``` c++
+ Velocity planning (Pose pose, Velocity velocity, Point goal, PointCloud *pointCloud, Config config);
+ ```
+
+ * **Parameters:**
+ - **pose:** [Pose](#pose)
+ - **velocity:** [Velocity](#velocity)
+ - **goal:** [Point](#point)
+ - **pointCloud:** [PointCloud](#pointcloud)
+ - **config:** [Config](#config)
+
+- Python
+
+ ``` python
+ linear_velocity, angular_velocity = planning(pose, velocity, goal, point_cloud, config)
+ ```
+
+ * **Parameters:**
+ - **pose:** tuple: (x, y, yaw)
+ - **velocity:** tuple: (Linear Velocity, Angular Velocity)
+ - **goal:** tuple: (x, y)
+ - **point_cloud:** Numpy Array of shape (N, 2) and type np.float32
+ - **config:** [Config](#config)
+
+#### createDynamicWindow
+
+Given robot configuration and current velocities, calculates [DynamicWindow](#dynamicwindow).
+The memory is allocated dynamically inside of the function and must be freed using
+[freeDynamicWindow](#freedynamicwindow) function.
+
+- C
+
+ ``` c++
+ void createDynamicWindow(Velocity velocity, Config config, DynamicWindow **dynamicWindow);
+ ```
+
+ * **Parameters:**
+ - **velocity:** [Velocity](#velocity)
+ - **config:** [Config](#config)
+ - **dynamicWindow:** [DynamicWindow](#dynamicwindow)
+
+- Python
+
+ [DynamicWindow](#dynamicwindow) class is used to create a DynamicWindow instance.
+
+ ``` python
+ dw = dwa.DynamicWindow(velocity, config):
+ print(dw.possible_v, dw.possible_w)
+ ```
+
+ * **Parameters:**
+ - **velocity:** tuple: (Linear Velocity, Angular Velocity)
+ - **config:** [Config](#config) class
+
+[Dynamic Window [2]](https://raw.githubusercontent.com/goktug97/DynamicWindowApproach/master/img/dynamic_window.jpg)
+
+#### freeDynamicWindow
+
+Free dynamically allocated memory.
+
+- C
+
+ ``` c++
+ void freeDynamicWindow(DynamicWindow *dynamicWindow);
+ ```
+
+ * **Parameters:**
+ - **dynamicWindow:** [DynamicWindow](#dynamicwindow)
+
+- Python
+
+ Handled by the [DynamicWindow](#dynamicwindow) class. See below.
+
+ ``` python
+ def __dealloc__(self):
+ if self.thisptr is not NULL:
+ cdwa.freeDynamicWindow(self.thisptr)
+
+ ```
+
+#### motion
+
+Given current position and velocities, calculates next position after
+given dt using differential drive motion model. Can be used to
+simulate motion in a simulated environment.
+
+- C
+
+ ``` c++
+ Pose motion(Pose pose, Velocity velocity, float dt);
+ ```
+
+ * **Parameters:**
+ - **pose:** [Pose](#pose)
+ - **velocity:** [Velocity](#velocity)
+ - **dt:** float (seconds)
+
+- Python
+
+ ``` python
+ x, y, yaw = motion(pose, velocity, dt)
+ ```
+
+ * **Parameters:**
+ - **pose:** tuple: (x, y, yaw)
+ - **velocity:** tuple: (Linear Velocity, Angular Velocity)
+
+#### calculateVelocityCost
+
+- C
+
+ ``` c++
+ float calculateVelocityCost(Velocity velocity, Config config);
+ ```
+
+ * **Parameters:**
+ - **velocity:** [Velocity](#velocity)
+ - **config:** [Config](#config)
+
+- Python
+
+ ``` python
+ velocity_cost = calculate_velocity_cost(velocity, config)
+ ```
+
+ * **Parameters:**
+ - **velocity:** tuple: (Linear Velocity, Angular Velocity)
+ - **config:** [Config](#config)
+
+#### calculateHeadingCost
+
+- C
+
+ ``` c++
+ float calculateHeadingCost(Pose pose, Point goal);
+ ```
+
+ * **Parameters:**
+ - **pose:** [Pose](#pose)
+ - **goal:** [Point](#point)
+
+- Python
+
+ ``` python
+ heading_cost = calculate_heading_cost(pose, goal)
+ ```
+
+ * **Parameters:**
+ - **pose:** tuple: (x, y, yaw)
+ - **goal:** tuple: (x, y)
+
+#### calculateClearanceCost
+
+- C
+
+ ``` c++
+ float calculateClearanceCost(Pose pose, Velocity velocity, PointCloud *pointCloud, Config config);
+ ```
+
+ * **Parameters:**
+ - **pose:** [Pose](#pose)
+ - **velocity:** [Velocity](#velocity)
+ - **pointCloud:** [PointCloud](#pointcloud)
+ - **config:** [Config](#config)
+
+- Python
+
+ ``` python
+ def calculate_clearance_cost(pose, velocity, point_cloud, config):
+ ```
+
+ * **Parameters:**
+ - **pose:** tuple: (x, y, yaw)
+ - **velocity:** tuple: (Linear Velocity, Angular Velocity)
+ - **point_cloud:** Numpy Array of shape (N, 2) and type np.float32
+ - **config:** [Config](#config)
+
+#### createPointCloud
+
+Given a size, creates a [PointCloud](#pointcloud). Must be freed using [freePointCloud](#freepointcloud).
+
+- C
+
+ ``` c++
+ PointCloud* createPointCloud(int size);
+ ```
+
+ ``` c++
+ for (int i = 0; i < pointCloud->size; ++i) {
+ pointCloud->points[i].x = 0.0
+ pointCloud->points[i].y = 0.0
+ }
+ ```
+
+ * **Parameters:**
+ - **size:** int
+
+- Python
+
+ [PointCloud](#pointcloud) class is used to create a PointCloud instance. All
+ functions in python accepts numpy array instead of PointCloud instance. The
+ PointCloud instance is created inside of the function.
+
+ ``` python
+ size = 600
+ point_cloud = np.zeros((size, 2), dtype=np.float32)
+ point_cloud = dwa.PointCloud(point_cloud)
+ ```
+
+#### freePointCloud
+
+- C
+
+ ``` c++
+ void freePointCloud(PointCloud* pointCloud);
+ ```
+
+ * **Parameters:**
+ - **pointCloud:** [PointCloud](#pointcloud)
+
+- Python
+
+ Handled by the [PointCloud](#pointcloud) class. See below.
+
+ ``` python
+ def __dealloc__(self):
+ if self.thisptr is not NULL:
+ cdwa.freePointCloud(self.thisptr)
+ ```
+
+## References
+
+
+
+1. D. Fox, W. Burgard and S. Thrun, "The dynamic window approach to
+collision avoidance," in IEEE Robotics & Automation Magazine, vol. 4,
+no. 1, pp. 23-33, March 1997. doi: 10.1109/100.580977 URL:
+http://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=580977&isnumber=12589
+
+
+
+2. http://ais.informatik.uni-freiburg.de/teaching/ss19/robotics/slides/19-pathplanning-long.pdf
+
+## License
MIT License.
diff --git a/dwa.gif b/dwa.gif
new file mode 100644
index 0000000..a519b5e
Binary files /dev/null and b/dwa.gif differ
diff --git a/examples/demo.c b/examples/demo.c
index b28ef62..ad1a652 100644
--- a/examples/demo.c
+++ b/examples/demo.c
@@ -12,6 +12,11 @@ void renderRectangle(Rect rect, Pose pose);
void renderPointCloud(PointCloud *pointCloud, int size);
void drawCircle(GLfloat x, GLfloat y, GLfloat radius);
+/*
+NOTE: 1 Unit = 0.1 m
+That's why everything is multiplied or divided by 10.
+*/
+
void initGL(void) {
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
@@ -28,7 +33,7 @@ void drawCircle(GLfloat x, GLfloat y, GLfloat radius){
glVertex2f(x, y);
for (int i = 0; i <= nTriangle; i++) {
glVertex2f(x + (radius * cos(i * M_PI2 / nTriangle)),
- y + (radius * sin(i * M_PI2 / nTriangle)));
+ y + (radius * sin(i * M_PI2 / nTriangle)));
}
glEnd();
}
@@ -45,8 +50,8 @@ void renderPointCloud(PointCloud *pointCloud, int size){
void renderRectangle(Rect rect, Pose pose) {
glPushMatrix();
glTranslatef(pose.point.x*10.0,
- pose.point.y*10.0,
- 0.0f);
+ pose.point.y*10.0,
+ 0.0f);
glRotatef(pose.yaw * 180.0/M_PI, 0, 0, 1);
glBegin(GL_POLYGON);
glColor3f(1, 0, 0);
@@ -117,44 +122,50 @@ int main() {
running = 0;
break;
case SDL_KEYDOWN:
- switch (sdlEvent.key.keysym.sym) {
- case SDLK_ESCAPE:
- running = 0;
- case SDLK_r:
- freePointCloud(pointCloud);
- pointCloud = createPointCloud(100);
- currentIdx = 0;
- pose.point.x = 0.0;
- pose.point.y = 0.0;
- pose.yaw = 0.0;
- }
- break;
+ switch (sdlEvent.key.keysym.sym) {
+ case SDLK_ESCAPE:
+ running = 0;
+ case SDLK_r:
+ freePointCloud(pointCloud);
+ pointCloud = createPointCloud(100);
+ currentIdx = 0;
+ pose.point.x = 0.0;
+ pose.point.y = 0.0;
+ pose.yaw = 0.0;
+ velocity.linearVelocity = 0.0;
+ velocity.angularVelocity = 0.0;
+ }
+ break;
case SDL_MOUSEBUTTONDOWN:
- drawing = 1;
+ drawing = 1;
break;
case SDL_MOUSEBUTTONUP:
- drawing = 0;
- break;
+ drawing = 0;
+ break;
case SDL_MOUSEMOTION:
- if (drawing) {
- pointCloud->points[currentIdx].x = (sdlEvent.button.x - RESOLUTION_WIDTH/2.0)/10.0;
- pointCloud->points[currentIdx].y = (-sdlEvent.button.y + RESOLUTION_HEIGHT/2.0)/10.0;
- currentIdx++;
- if (!(currentIdx % 100)) {
- tmpPointCloud = createPointCloud(currentIdx);
- memcpy(tmpPointCloud->points, pointCloud->points, currentIdx*sizeof(Point));
- freePointCloud(pointCloud);
- pointCloud = createPointCloud(currentIdx+100);
- memcpy(pointCloud->points, tmpPointCloud->points, currentIdx*sizeof(Point));
- freePointCloud(tmpPointCloud);
- }
- } else {
- goal.x = (sdlEvent.button.x - RESOLUTION_WIDTH/2.0)/10.0;
- goal.y = (-sdlEvent.button.y + RESOLUTION_HEIGHT/2.0)/10.0;
- glColor3f(0, 1, 0);
- drawCircle(goal.x*10, goal.y*10, 3);
- }
- break;
+ if (drawing) {
+ pointCloud->points[currentIdx].x = (sdlEvent.button.x -
+ RESOLUTION_WIDTH/2.0)/10.0;
+ pointCloud->points[currentIdx].y = (-sdlEvent.button.y +
+ RESOLUTION_HEIGHT/2.0)/10.0;
+ currentIdx++;
+ if (!(currentIdx % 100)) {
+ tmpPointCloud = createPointCloud(currentIdx);
+ memcpy(tmpPointCloud->points, pointCloud->points,
+ currentIdx*sizeof(Point));
+ freePointCloud(pointCloud);
+ pointCloud = createPointCloud(currentIdx+100);
+ memcpy(pointCloud->points, tmpPointCloud->points,
+ currentIdx*sizeof(Point));
+ freePointCloud(tmpPointCloud);
+ }
+ } else {
+ goal.x = (sdlEvent.button.x - RESOLUTION_WIDTH/2.0)/10.0;
+ goal.y = (-sdlEvent.button.y + RESOLUTION_HEIGHT/2.0)/10.0;
+ glColor3f(0, 1, 0);
+ drawCircle(goal.x*10, goal.y*10, 3);
+ }
+ break;
}
}
if (currentIdx) {
diff --git a/examples/demo.py b/examples/demo.py
index f94ff75..574547d 100644
--- a/examples/demo.py
+++ b/examples/demo.py
@@ -9,6 +9,7 @@
class Demo(object):
def __init__(self):
# 1 px = 0.1 m
+ # That's why everything is multiplied or divided by 10.
cv2.namedWindow('cvwindow')
cv2.setMouseCallback('cvwindow', self.callback)
self.drawing = False
diff --git a/img/dynamic_window.jpg b/img/dynamic_window.jpg
new file mode 100644
index 0000000..341a963
Binary files /dev/null and b/img/dynamic_window.jpg differ
diff --git a/src/dwa.c b/src/dwa.c
index f2d58ed..b65ec79 100644
--- a/src/dwa.c
+++ b/src/dwa.c
@@ -89,14 +89,14 @@ calculateClearanceCost
x = -dx * cos(pPose.yaw) + -dy * sin(pPose.yaw);
y = -dx * -sin(pPose.yaw) + -dy * cos(pPose.yaw);
if (x <= config.base.xmax &&
- x >= config.base.xmin &&
- y <= config.base.ymax &&
- y >= config.base.ymin){
- return FLT_MAX;
+ x >= config.base.xmin &&
+ y <= config.base.ymax &&
+ y >= config.base.ymin){
+ return FLT_MAX;
}
r = sqrtf(dx*dx + dy*dy);
if (r < minr)
- minr = r;
+ minr = r;
}
time += config.dt;
}
@@ -105,7 +105,7 @@ calculateClearanceCost
Velocity
planning(Pose pose, Velocity velocity, Point goal,
- PointCloud *pointCloud, Config config){
+ PointCloud *pointCloud, Config config) {
DynamicWindow *dw;
createDynamicWindow(velocity, config, &dw);
Velocity pVelocity;
@@ -120,12 +120,13 @@ planning(Pose pose, Velocity velocity, Point goal,
pVelocity.angularVelocity = dw->possibleW[j];
pPose = motion(pPose, pVelocity, config.predictTime);
cost =
- config.velocity * calculateVelocityCost(pVelocity, config) +
- config.heading * calculateHeadingCost(pPose, goal) +
- config.clearance * calculateClearanceCost(pose, pVelocity, pointCloud, config);
+ config.velocity * calculateVelocityCost(pVelocity, config) +
+ config.heading * calculateHeadingCost(pPose, goal) +
+ config.clearance * calculateClearanceCost(pose, pVelocity,
+ pointCloud, config);
if (cost < total_cost) {
- total_cost = cost;
- bestVelocity = pVelocity;
+ total_cost = cost;
+ bestVelocity = pVelocity;
}
}
}