Skip to content

Commit bfa897c

Browse files
author
Tullio Facchinetti
committed
Simulation that shows how the configuration space is populated with obstacles
1 parent 6c90fb7 commit bfa897c

1 file changed

Lines changed: 214 additions & 0 deletions

File tree

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
"""
2+
Obstacle navigation using A* on a toroidal grid
3+
4+
Author: Daniel Ingram (daniel-s-ingram)
5+
Tullio Facchinetti (tullio.facchinetti@unipv.it)
6+
"""
7+
from math import pi
8+
import numpy as np
9+
import matplotlib.pyplot as plt
10+
from matplotlib.colors import from_levels_and_colors
11+
import sys
12+
13+
plt.ion()
14+
15+
# Simulation parameters
16+
# number of squares to divide the x-y axes of the configuration space
17+
M = 200
18+
M = 50
19+
M = 10
20+
delay = 0.5 # delay between consecutive frames in the simulation
21+
obstacles = [[1.75, 0.3, 0.6], [0.55, 1.5, 0.5], [0, -1, 0.7]]
22+
23+
24+
in_pause = False
25+
26+
27+
def press(event):
28+
"""Exit from the simulation."""
29+
global in_pause
30+
if event.key == 'q' or event.key == 'Q':
31+
print('Quitting upon request.')
32+
sys.exit(0)
33+
if event.key == ' ':
34+
in_pause = not in_pause
35+
36+
37+
def main():
38+
# Arm geometry in the working space
39+
link_length = [0.5, 1.5]
40+
initial_link_angle = [0, 0]
41+
arm = NLinkArm(link_length, initial_link_angle)
42+
# (x, y) co-ordinates in the joint space [cell]
43+
start = (int(10 / 100 * M), int(50 / 100 * M))
44+
goal = (int(54 / 100 * M), int(60 / 100 * M))
45+
animate(arm)
46+
47+
48+
def animate(arm):
49+
global in_pause
50+
fig, axs = plt.subplots(1, 2)
51+
fig.canvas.mpl_connect('key_press_event', press)
52+
colors = ['white', 'black', 'red', 'pink', 'yellow', 'green', 'orange']
53+
levels = [0, 1, 2, 3, 4, 5, 6, 7]
54+
cmap, norm = from_levels_and_colors(levels, colors)
55+
grid = [[0 for _ in range(M)] for _ in range(M)]
56+
theta_list = np.linspace(-pi, pi, M, endpoint=False)
57+
for i in range(M):
58+
for j in range(M):
59+
arm.update_joints([theta_list[i], theta_list[j]])
60+
points = arm.points
61+
collision_detected = False
62+
for k in range(len(points) - 1):
63+
for obstacle in obstacles:
64+
line_seg = [points[k], points[k + 1]]
65+
collision_detected = detect_collision(line_seg, obstacle)
66+
if collision_detected:
67+
break
68+
if collision_detected:
69+
break
70+
if collision_detected:
71+
grid[i][j] = 1
72+
else:
73+
grid[i][j] = 5
74+
plt.subplot(1, 2, 1)
75+
#grid[node] = 6
76+
plt.cla()
77+
plt.imshow(grid, cmap=cmap, norm=norm, interpolation=None)
78+
arm.update_joints([theta_list[i], theta_list[j]])
79+
plt.subplot(1, 2, 2)
80+
arm.plot_arm(plt, obstacles=obstacles)
81+
plt.xlim(-2.0, 2.0)
82+
plt.ylim(-3.0, 3.0)
83+
plt.show()
84+
# Uncomment here to save the sequence of frames
85+
# plt.savefig('frame{:04d}.png'.format(i))
86+
while in_pause:
87+
plt.pause(delay)
88+
plt.pause(delay)
89+
in_pause = True
90+
while in_pause:
91+
plt.pause(delay)
92+
93+
94+
def detect_collision(line_seg, circle):
95+
"""
96+
Determines whether a line segment (arm link) is in contact
97+
with a circle (obstacle).
98+
Credit to: http://doswa.com/2009/07/13/circle-segment-intersectioncollision.html
99+
Args:
100+
line_seg: List of coordinates of line segment endpoints e.g. [[1, 1], [2, 2]]
101+
circle: List of circle coordinates and radius e.g. [0, 0, 0.5] is a circle centered
102+
at the origin with radius 0.5
103+
104+
Returns:
105+
True if the line segment is in contact with the circle
106+
False otherwise
107+
"""
108+
a_vec = np.array([line_seg[0][0], line_seg[0][1]])
109+
b_vec = np.array([line_seg[1][0], line_seg[1][1]])
110+
c_vec = np.array([circle[0], circle[1]])
111+
radius = circle[2]
112+
line_vec = b_vec - a_vec
113+
line_mag = np.linalg.norm(line_vec)
114+
circle_vec = c_vec - a_vec
115+
proj = circle_vec.dot(line_vec / line_mag)
116+
if proj <= 0:
117+
closest_point = a_vec
118+
elif proj >= line_mag:
119+
closest_point = b_vec
120+
else:
121+
closest_point = a_vec + line_vec * proj / line_mag
122+
if np.linalg.norm(closest_point - c_vec) > radius:
123+
return False
124+
return True
125+
126+
127+
def get_occupancy_grid(arm, obstacles):
128+
"""
129+
Discretizes joint space into M values from -pi to +pi
130+
and determines whether a given coordinate in joint space
131+
would result in a collision between a robot arm and obstacles
132+
in its environment.
133+
134+
Args:
135+
arm: An instance of NLinkArm
136+
obstacles: A list of obstacles, with each obstacle defined as a list
137+
of xy coordinates and a radius.
138+
139+
Returns:
140+
Occupancy grid in joint space
141+
"""
142+
grid = [[0 for _ in range(M)] for _ in range(M)]
143+
theta_list = [2 * i * pi / M for i in range(-M // 2, M // 2 + 1)]
144+
for i in range(M):
145+
for j in range(M):
146+
arm.update_joints([theta_list[i], theta_list[j]])
147+
points = arm.points
148+
collision_detected = False
149+
for k in range(len(points) - 1):
150+
for obstacle in obstacles:
151+
line_seg = [points[k], points[k + 1]]
152+
collision_detected = detect_collision(line_seg, obstacle)
153+
if collision_detected:
154+
break
155+
if collision_detected:
156+
break
157+
grid[i][j] = int(collision_detected)
158+
return np.array(grid)
159+
160+
161+
class NLinkArm(object):
162+
"""
163+
Class for controlling and plotting a planar arm with an arbitrary number of links.
164+
"""
165+
166+
def __init__(self, link_lengths, joint_angles):
167+
self.n_links = len(link_lengths)
168+
if self.n_links != len(joint_angles):
169+
raise ValueError()
170+
171+
self.link_lengths = np.array(link_lengths)
172+
self.joint_angles = np.array(joint_angles)
173+
self.points = [[0, 0] for _ in range(self.n_links + 1)]
174+
175+
self.lim = sum(link_lengths)
176+
self.update_points()
177+
178+
def update_joints(self, joint_angles):
179+
self.joint_angles = joint_angles
180+
self.update_points()
181+
182+
def update_points(self):
183+
for i in range(1, self.n_links + 1):
184+
self.points[i][0] = self.points[i - 1][0] + \
185+
self.link_lengths[i - 1] * \
186+
np.cos(np.sum(self.joint_angles[:i]))
187+
self.points[i][1] = self.points[i - 1][1] + \
188+
self.link_lengths[i - 1] * \
189+
np.sin(np.sum(self.joint_angles[:i]))
190+
191+
self.end_effector = np.array(self.points[self.n_links]).T
192+
193+
def plot_arm(self, myplt, obstacles=[]): # pragma: no cover
194+
myplt.cla()
195+
196+
for obstacle in obstacles:
197+
circle = myplt.Circle(
198+
(obstacle[0], obstacle[1]), radius=obstacle[2], fc='k')
199+
myplt.gca().add_patch(circle)
200+
201+
for i in range(self.n_links + 1):
202+
if i is not self.n_links:
203+
myplt.plot([self.points[i][0], self.points[i + 1][0]],
204+
[self.points[i][1], self.points[i + 1][1]], 'r-')
205+
myplt.plot(self.points[i][0], self.points[i][1], 'k.')
206+
207+
myplt.xlim([-self.lim, self.lim])
208+
myplt.ylim([-self.lim, self.lim])
209+
myplt.draw()
210+
# myplt.pause(1e-5)
211+
212+
213+
if __name__ == '__main__':
214+
main()

0 commit comments

Comments
 (0)