1010import math
1111import matplotlib .pyplot as plt
1212
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+
1318# Parameters
1419k = 0.1 # look forward gain
1520Lfc = 2.0 # [m] look-ahead distance
1621Kp = 1.0 # speed proportional gain
1722dt = 0.1 # [s] time tick
1823WB = 2.9 # [m] wheel base of vehicle
1924
20- show_animation = True
2125
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]
2232
23- class State :
2433
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 ):
2640 self .x = x
2741 self .y = y
2842 self .yaw = yaw
2943 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 ))
3247
3348 def update (self , a , delta ):
3449 self .x += self .v * math .cos (self .yaw ) * dt
3550 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 )
3753 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 ))
4056
4157 def calc_distance (self , point_x , point_y ):
4258 dx = self .rear_x - point_x
@@ -51,13 +67,15 @@ def __init__(self):
5167 self .y = []
5268 self .yaw = []
5369 self .v = []
70+ self .direction = []
5471 self .t = []
5572
5673 def append (self , t , state ):
5774 self .x .append (state .x )
5875 self .y .append (state .y )
5976 self .yaw .append (state .yaw )
6077 self .v .append (state .v )
78+ self .direction .append (state .direction )
6179 self .t .append (t )
6280
6381
@@ -117,14 +135,18 @@ def pure_pursuit_steer_control(state, trajectory, pind):
117135 if ind < len (trajectory .cx ):
118136 tx = trajectory .cx [ind ]
119137 ty = trajectory .cy [ind ]
120- else : # toward goal
138+ else :
121139 tx = trajectory .cx [- 1 ]
122140 ty = trajectory .cy [- 1 ]
123141 ind = len (trajectory .cx ) - 1
124142
125143 alpha = math .atan2 (ty - state .rear_y , tx - state .rear_x ) - state .yaw
126144
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 )
128150
129151 return delta , ind
130152
@@ -142,18 +164,119 @@ def plot_arrow(x, y, yaw, length=1.0, width=0.5, fc="r", ec="k"):
142164 fc = fc , ec = ec , head_width = width , head_length = width )
143165 plt .plot (x , y )
144166
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 )
145268
146269def main ():
147270 # 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 )
149272 cy = [math .sin (ix / 5.0 ) * ix / 2.0 for ix in cx ]
150273
151274 target_speed = 10.0 / 3.6 # [m/s]
152275
153276 T = 100.0 # max simulation time
154277
155278 # 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 )
157280
158281 lastIndex = len (cx ) - 1
159282 time = 0.0
@@ -173,22 +296,33 @@ def main():
173296
174297 time += dt
175298 states .append (time , state )
176-
177299 if show_animation : # pragma: no cover
178300 plt .cla ()
179301 # 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 )
184305 plt .plot (cx , cy , "-r" , label = "course" )
185306 plt .plot (states .x , states .y , "-b" , label = "trajectory" )
186307 plt .plot (cx [target_ind ], cy [target_ind ], "xg" , label = "target" )
187308 plt .axis ("equal" )
188309 plt .grid (True )
189310 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+
190318 plt .pause (0.001 )
191319
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+
192326 # Test
193327 assert lastIndex >= target_ind , "Cannot goal"
194328
0 commit comments