10
10
import math
11
11
import matplotlib .pyplot as plt
12
12
13
+ import sys
14
+ import pathlib
15
+ sys .path .append (str (pathlib .Path (__file__ ).parent .parent .parent ))
16
+ from utils .angle import angle_mod
17
+
13
18
# Parameters
14
19
k = 0.1 # look forward gain
15
20
Lfc = 2.0 # [m] look-ahead distance
16
21
Kp = 1.0 # speed proportional gain
17
22
dt = 0.1 # [s] time tick
18
23
WB = 2.9 # [m] wheel base of vehicle
19
24
20
- show_animation = True
21
25
26
+ # Vehicle parameters
27
+ LENGTH = WB + 1.0 # Vehicle length
28
+ WIDTH = 2.0 # Vehicle width
29
+ WHEEL_LEN = 0.6 # Wheel length
30
+ WHEEL_WIDTH = 0.2 # Wheel width
31
+ MAX_STEER = math .pi / 4 # Maximum steering angle [rad]
22
32
23
- class State :
24
33
25
- def __init__ (self , x = 0.0 , y = 0.0 , yaw = 0.0 , v = 0.0 ):
34
+ show_animation = True
35
+ pause_simulation = False # Flag for pause simulation
36
+ is_reverse_mode = False # Flag for reverse driving mode
37
+
38
+ class State :
39
+ def __init__ (self , x = 0.0 , y = 0.0 , yaw = 0.0 , v = 0.0 , is_reverse = False ):
26
40
self .x = x
27
41
self .y = y
28
42
self .yaw = yaw
29
43
self .v = v
30
- self .rear_x = self .x - ((WB / 2 ) * math .cos (self .yaw ))
31
- self .rear_y = self .y - ((WB / 2 ) * math .sin (self .yaw ))
44
+ self .direction = - 1 if is_reverse else 1 # Direction based on reverse flag
45
+ self .rear_x = self .x - self .direction * ((WB / 2 ) * math .cos (self .yaw ))
46
+ self .rear_y = self .y - self .direction * ((WB / 2 ) * math .sin (self .yaw ))
32
47
33
48
def update (self , a , delta ):
34
49
self .x += self .v * math .cos (self .yaw ) * dt
35
50
self .y += self .v * math .sin (self .yaw ) * dt
36
- self .yaw += self .v / WB * math .tan (delta ) * dt
51
+ self .yaw += self .direction * self .v / WB * math .tan (delta ) * dt
52
+ self .yaw = angle_mod (self .yaw )
37
53
self .v += a * dt
38
- self .rear_x = self .x - ((WB / 2 ) * math .cos (self .yaw ))
39
- self .rear_y = self .y - ((WB / 2 ) * math .sin (self .yaw ))
54
+ self .rear_x = self .x - self . direction * ((WB / 2 ) * math .cos (self .yaw ))
55
+ self .rear_y = self .y - self . direction * ((WB / 2 ) * math .sin (self .yaw ))
40
56
41
57
def calc_distance (self , point_x , point_y ):
42
58
dx = self .rear_x - point_x
@@ -51,13 +67,15 @@ def __init__(self):
51
67
self .y = []
52
68
self .yaw = []
53
69
self .v = []
70
+ self .direction = []
54
71
self .t = []
55
72
56
73
def append (self , t , state ):
57
74
self .x .append (state .x )
58
75
self .y .append (state .y )
59
76
self .yaw .append (state .yaw )
60
77
self .v .append (state .v )
78
+ self .direction .append (state .direction )
61
79
self .t .append (t )
62
80
63
81
@@ -117,14 +135,18 @@ def pure_pursuit_steer_control(state, trajectory, pind):
117
135
if ind < len (trajectory .cx ):
118
136
tx = trajectory .cx [ind ]
119
137
ty = trajectory .cy [ind ]
120
- else : # toward goal
138
+ else :
121
139
tx = trajectory .cx [- 1 ]
122
140
ty = trajectory .cy [- 1 ]
123
141
ind = len (trajectory .cx ) - 1
124
142
125
143
alpha = math .atan2 (ty - state .rear_y , tx - state .rear_x ) - state .yaw
126
144
127
- delta = math .atan2 (2.0 * WB * math .sin (alpha ) / Lf , 1.0 )
145
+ # Reverse steering angle when reversing
146
+ delta = state .direction * math .atan2 (2.0 * WB * math .sin (alpha ) / Lf , 1.0 )
147
+
148
+ # Limit steering angle to max value
149
+ delta = np .clip (delta , - MAX_STEER , MAX_STEER )
128
150
129
151
return delta , ind
130
152
@@ -142,18 +164,119 @@ def plot_arrow(x, y, yaw, length=1.0, width=0.5, fc="r", ec="k"):
142
164
fc = fc , ec = ec , head_width = width , head_length = width )
143
165
plt .plot (x , y )
144
166
167
+ def plot_vehicle (x , y , yaw , steer = 0.0 , color = 'blue' , is_reverse = False ):
168
+ """
169
+ Plot vehicle model with four wheels
170
+ Args:
171
+ x, y: Vehicle center position
172
+ yaw: Vehicle heading angle
173
+ steer: Steering angle
174
+ color: Vehicle color
175
+ is_reverse: Flag for reverse mode
176
+ """
177
+ # Adjust heading angle in reverse mode
178
+ if is_reverse :
179
+ yaw = angle_mod (yaw + math .pi ) # Rotate heading by 180 degrees
180
+ steer = - steer # Reverse steering direction
181
+
182
+ def plot_wheel (x , y , yaw , steer = 0.0 , color = color ):
183
+ """Plot single wheel"""
184
+ wheel = np .array ([
185
+ [- WHEEL_LEN / 2 , WHEEL_WIDTH / 2 ],
186
+ [WHEEL_LEN / 2 , WHEEL_WIDTH / 2 ],
187
+ [WHEEL_LEN / 2 , - WHEEL_WIDTH / 2 ],
188
+ [- WHEEL_LEN / 2 , - WHEEL_WIDTH / 2 ],
189
+ [- WHEEL_LEN / 2 , WHEEL_WIDTH / 2 ]
190
+ ])
191
+
192
+ # Rotate wheel if steering
193
+ if steer != 0 :
194
+ c , s = np .cos (steer ), np .sin (steer )
195
+ rot_steer = np .array ([[c , - s ], [s , c ]])
196
+ wheel = wheel @ rot_steer .T
197
+
198
+ # Apply vehicle heading rotation
199
+ c , s = np .cos (yaw ), np .sin (yaw )
200
+ rot_yaw = np .array ([[c , - s ], [s , c ]])
201
+ wheel = wheel @ rot_yaw .T
202
+
203
+ # Translate to position
204
+ wheel [:, 0 ] += x
205
+ wheel [:, 1 ] += y
206
+
207
+ # Plot wheel with color
208
+ plt .plot (wheel [:, 0 ], wheel [:, 1 ], color = color )
209
+
210
+ # Calculate vehicle body corners
211
+ corners = np .array ([
212
+ [- LENGTH / 2 , WIDTH / 2 ],
213
+ [LENGTH / 2 , WIDTH / 2 ],
214
+ [LENGTH / 2 , - WIDTH / 2 ],
215
+ [- LENGTH / 2 , - WIDTH / 2 ],
216
+ [- LENGTH / 2 , WIDTH / 2 ]
217
+ ])
218
+
219
+ # Rotation matrix
220
+ c , s = np .cos (yaw ), np .sin (yaw )
221
+ Rot = np .array ([[c , - s ], [s , c ]])
222
+
223
+ # Rotate and translate vehicle body
224
+ rotated = corners @ Rot .T
225
+ rotated [:, 0 ] += x
226
+ rotated [:, 1 ] += y
227
+
228
+ # Plot vehicle body
229
+ plt .plot (rotated [:, 0 ], rotated [:, 1 ], color = color )
230
+
231
+ # Plot wheels (darker color for front wheels)
232
+ front_color = 'darkblue'
233
+ rear_color = color
234
+
235
+ # Plot four wheels
236
+ # Front left
237
+ plot_wheel (x + LENGTH / 4 * c - WIDTH / 2 * s ,
238
+ y + LENGTH / 4 * s + WIDTH / 2 * c ,
239
+ yaw , steer , front_color )
240
+ # Front right
241
+ plot_wheel (x + LENGTH / 4 * c + WIDTH / 2 * s ,
242
+ y + LENGTH / 4 * s - WIDTH / 2 * c ,
243
+ yaw , steer , front_color )
244
+ # Rear left
245
+ plot_wheel (x - LENGTH / 4 * c - WIDTH / 2 * s ,
246
+ y - LENGTH / 4 * s + WIDTH / 2 * c ,
247
+ yaw , color = rear_color )
248
+ # Rear right
249
+ plot_wheel (x - LENGTH / 4 * c + WIDTH / 2 * s ,
250
+ y - LENGTH / 4 * s - WIDTH / 2 * c ,
251
+ yaw , color = rear_color )
252
+
253
+ # Add direction arrow
254
+ arrow_length = LENGTH / 3
255
+ plt .arrow (x , y ,
256
+ - arrow_length * math .cos (yaw ) if is_reverse else arrow_length * math .cos (yaw ),
257
+ - arrow_length * math .sin (yaw ) if is_reverse else arrow_length * math .sin (yaw ),
258
+ head_width = WIDTH / 4 , head_length = WIDTH / 4 ,
259
+ fc = 'r' , ec = 'r' , alpha = 0.5 )
260
+
261
+ # Keyboard event handler
262
+ def on_key (event ):
263
+ global pause_simulation
264
+ if event .key == ' ' : # Space key
265
+ pause_simulation = not pause_simulation
266
+ elif event .key == 'escape' :
267
+ exit (0 )
145
268
146
269
def main ():
147
270
# target course
148
- cx = np .arange (0 , 50 , 0.5 )
271
+ cx = - 1 * np . arange ( 0 , 50 , 0.5 ) if is_reverse_mode else np .arange (0 , 50 , 0.5 )
149
272
cy = [math .sin (ix / 5.0 ) * ix / 2.0 for ix in cx ]
150
273
151
274
target_speed = 10.0 / 3.6 # [m/s]
152
275
153
276
T = 100.0 # max simulation time
154
277
155
278
# initial state
156
- state = State (x = - 0.0 , y = - 3.0 , yaw = 0.0 , v = 0.0 )
279
+ state = State (x = - 0.0 , y = - 3.0 , yaw = math . pi if is_reverse_mode else 0.0 , v = 0.0 , is_reverse = is_reverse_mode )
157
280
158
281
lastIndex = len (cx ) - 1
159
282
time = 0.0
@@ -173,22 +296,33 @@ def main():
173
296
174
297
time += dt
175
298
states .append (time , state )
176
-
177
299
if show_animation : # pragma: no cover
178
300
plt .cla ()
179
301
# for stopping simulation with the esc key.
180
- plt .gcf ().canvas .mpl_connect (
181
- 'key_release_event' ,
182
- lambda event : [exit (0 ) if event .key == 'escape' else None ])
183
- plot_arrow (state .x , state .y , state .yaw )
302
+ plt .gcf ().canvas .mpl_connect ('key_release_event' , on_key )
303
+ # Pass is_reverse parameter
304
+ plot_vehicle (state .x , state .y , state .yaw , di , is_reverse = is_reverse_mode )
184
305
plt .plot (cx , cy , "-r" , label = "course" )
185
306
plt .plot (states .x , states .y , "-b" , label = "trajectory" )
186
307
plt .plot (cx [target_ind ], cy [target_ind ], "xg" , label = "target" )
187
308
plt .axis ("equal" )
188
309
plt .grid (True )
189
310
plt .title ("Speed[km/h]:" + str (state .v * 3.6 )[:4 ])
311
+ plt .legend () # Add legend display
312
+
313
+ # Add pause state display
314
+ if pause_simulation :
315
+ plt .text (0.02 , 0.95 , 'PAUSED' , transform = plt .gca ().transAxes ,
316
+ bbox = dict (facecolor = 'red' , alpha = 0.5 ))
317
+
190
318
plt .pause (0.001 )
191
319
320
+ # Handle pause state
321
+ while pause_simulation :
322
+ plt .pause (0.1 ) # Reduce CPU usage
323
+ if not plt .get_fignums (): # Check if window is closed
324
+ exit (0 )
325
+
192
326
# Test
193
327
assert lastIndex >= target_ind , "Cannot goal"
194
328
0 commit comments