@@ -175,6 +175,146 @@ def waypoint_generate(vehicle_state, cones, cone_idx):
175175 target_heading -= 2 * np .pi
176176
177177 return scenario , flex_wps_list , fixed_wp , target_heading
178+
179+ def waypoint_generate_relaxed (vehicle_state , cones , cone_idx ):
180+ """
181+ Generate waypoints based on the scenario.
182+ Args:
183+ vehicle_state: The state of the vehicle.
184+ cone_state: The state of the cones.
185+ Returns:
186+ Tuple[str, Tuple[float, float], Tuple[float, float]]:
187+ scenario: detected scenario type
188+ flex_wp: flexible waypoint (used to maneuver)
189+ fixed_wp: fixed waypoint (goal position)
190+ """
191+ scenario , cone_position = scenario_check (vehicle_state , [cones [cone_idx ]])
192+ car_position = np .array (vehicle_state ['position' ])
193+ car_heading = vehicle_state ['heading' ] # in radians
194+ current_heading = car_heading
195+ target_heading = car_heading
196+
197+ # ===== Parameters =====
198+ u_turn_radius = 11.5 # Radius for U-turn
199+ offset = 2.0 # Offset for left/right pass
200+ lookahead_distance = 10.0 # Distance ahead for fixed point
201+ # ======================
202+
203+ # Direction vector based on heading
204+ heading_vector = np .array ([np .cos (car_heading ), np .sin (car_heading )])
205+
206+ # Vector perpendicular to heading (to determine left/right)
207+ perpendicular_vector = np .array ([- np .sin (car_heading ), np .cos (car_heading )])
208+
209+ if cone_idx > 0 :
210+ prev_cone_position = np .array ((cones [cone_idx - 1 ]['x' ], cones [cone_idx - 1 ]['y' ]))
211+ cone_position = np .array ((cones [cone_idx ]['x' ], cones [cone_idx ]['y' ]))
212+ target_heading = np .arctan2 (cone_position [1 ] - prev_cone_position [1 ],
213+ cone_position [0 ] - prev_cone_position [0 ])
214+
215+ if scenario == 'standing' :
216+ # U-turn: Generate points in a semi-circular arc around the cone
217+ cone = np .array (cone_position )
218+
219+ # Number of waypoints to generate for the arc
220+ num_arc_points = 9
221+
222+ # Generate waypoints in a smooth semi-circular pattern on the RIGHT side
223+ flex_wps_list = []
224+
225+ # Create a semi-circular arc from 0 to π
226+ # But negate perpendicular_vector to go to the right side
227+ for i in range (num_arc_points ):
228+ # Calculate angle in the semi-circle (0 to π)
229+ angle = i * np .pi * 1 / (num_arc_points - 1 )
230+
231+ # MODIFIED: Negative perpendicular_vector to go to right side of cone
232+ # This creates a counter-clockwise turn around the cone
233+ wp = cone - u_turn_radius * (
234+ perpendicular_vector * np .cos (angle ) -
235+ heading_vector * np .sin (angle )
236+ )
237+
238+ flex_wps_list .append (wp )
239+
240+ # Fixed waypoint: behind the cone after U-turn (also on right side)
241+ fixed_wp = cone + u_turn_radius * perpendicular_vector
242+
243+ target_heading = target_heading + np .pi
244+
245+ elif scenario == 'left' :
246+ cone = np .array (cone_position )
247+
248+ # Flexible waypoint: go to the left of the cone
249+ flex_wp1 = cone + offset * perpendicular_vector
250+ flex_wps_list = [flex_wp1 ]
251+
252+ # Fixed waypoint: forward after passing the cone
253+ # fixed_wp = flex_wp + heading_vector * lookahead_distance - offset * perpendicular_vector * 2
254+ fixed_wp = flex_wp1 + heading_vector * lookahead_distance - offset * perpendicular_vector
255+
256+ elif scenario == 'right' :
257+ cone = np .array (cone_position )
258+
259+ # Flexible waypoint: go to the right of the cone
260+ flex_wp1 = cone - offset * perpendicular_vector
261+ flex_wps_list = [flex_wp1 ]
262+
263+ # Fixed waypoint: forward after passing the cone
264+ # fixed_wp = flex_wp + heading_vector * lookahead_distance + offset * perpendicular_vector * 2
265+ fixed_wp = flex_wp1 + heading_vector * lookahead_distance + offset * perpendicular_vector
266+
267+ # TODO: move to a different setting instead of as a scenario
268+ elif scenario == '90turn' :
269+ turn_vector = np .array ([- np .sin (car_heading + np .pi / 6 ), np .cos (car_heading + np .pi / 6 )])
270+ cone = np .array (cone_position )
271+ distance_to_cone = np .linalg .norm (car_position - cone )
272+
273+ flex_wps_list = []
274+ if distance_to_cone > 7 :
275+ steps = int (distance_to_cone // 6 )
276+ print ("steps:" , steps )
277+
278+ final_flex_wp = cone + 1.0 * perpendicular_vector + heading_vector * 1.0
279+ fixed_wp = cone - turn_vector * 2.5 + heading_vector * 0.0
280+
281+ cone_direction = (final_flex_wp - car_position ) / np .linalg .norm (final_flex_wp - car_position )
282+ for i in range (steps - 2 ):
283+ intermediate_wp = car_position + cone_direction * ((i + 1 ) * 6 )
284+ flex_wps_list .append (intermediate_wp )
285+
286+ flex_wps_list .append (final_flex_wp )
287+ else :
288+ flex_wp = cone + 1.0 * perpendicular_vector + heading_vector * 1.0
289+ fixed_wp = cone - turn_vector * 2.5 + heading_vector * 0.0
290+ flex_wps_list = [flex_wp ]
291+
292+ else :
293+ flex_wps_list = None
294+ fixed_wp = None
295+
296+ target_heading = (target_heading + np .pi ) % (2 * np .pi ) - np .pi
297+ current_heading = (current_heading + np .pi ) % (2 * np .pi ) - np .pi
298+
299+ if abs (target_heading - current_heading ) > np .pi :
300+ if target_heading < current_heading :
301+ target_heading += 2 * np .pi
302+ else :
303+ target_heading -= 2 * np .pi
304+
305+ # For visualization only
306+ for i , wp in enumerate (flex_wps_list ):
307+ plt .plot (wp [0 ], wp [1 ], 'ro' ) # Plot all waypoints in red
308+ plt .text (wp [0 ], wp [1 ], f'wp{ i } ' , fontsize = 9 )
309+
310+ plt .plot (fixed_wp [0 ], fixed_wp [1 ], 'bo' ) # Plot fixed waypoint in blue
311+ plt .plot (cone [0 ], cone [1 ], 'gx' ) # Plot cone in green
312+ plt .plot (car_position [0 ], car_position [1 ], 'ks' ) # Plot car position
313+ plt .axis ('equal' ) # Equal aspect ratio for better visualization
314+ plt .grid (True )
315+ plt .show ()
316+
317+ return scenario , flex_wps_list , fixed_wp , target_heading
178318
179319def velocity_profiling (path , acceleration , deceleration , max_speed , current_speed , lateral_acc_limit ):
180320 """
@@ -431,6 +571,172 @@ def dynamics_constraints(p):
431571
432572 return x_full , y_full , psi_full , c_full , v_full , eps_ , final_error
433573
574+ ######## more dynamics version
575+ def trajectory_generation_dynamics (init_state , final_state , N = 30 , Lr = 1.5 ,
576+ a_min = - 3.0 , a_max = 3.0 ,
577+ eps_min = - 0.2 , eps_max = 0.2 ,
578+ v_min = 3.0 , v_max = 11.0 ,
579+ T_min = 0.5 , T_max = 500.0 ,
580+ waypoints = None , waypoint_penalty_weight = 1.0 ):
581+ """
582+ Generate a dynamically feasible trajectory between init_state and final_state
583+ using curvature-based vehicle dynamics and nonlinear optimization.
584+
585+ Now supports multiple waypoints.
586+
587+ Parameters:
588+ - init_state (dict): Initial vehicle state with keys 'x', 'y', 'psi', 'c', 'v'.
589+ - final_state (dict): Target vehicle state with keys 'x', 'y', 'psi', 'c'.
590+ - N (int): Number of discrete time steps in the trajectory.
591+ - T (float): Duration of each time step (in seconds).
592+ - Lr (float): Distance from the vehicle's center to the rear axle.
593+ - w_c (float): Weight for penalizing curvature (smoothness of turns).
594+ - w_eps (float): Weight for penalizing curvature rate (reduces sharp steering changes).
595+ - w_vvar (float): Weight for penalizing speed variance (encourages speed smoothness).
596+ - w_terminal (float): Weight for penalizing final state deviation (soft constraint).
597+ - v_min (float): Minimum allowed speed (in m/s).
598+ - v_max (float): Maximum allowed speed (in m/s).
599+ - waypoints (list or None): Optional list of (x, y) coordinates that the trajectory should pass near.
600+ - waypoint_penalty_weight (float): Penalty weight for distance from waypoints (soft constraint).
601+
602+ Returns:
603+ - x, y, psi, c, v, eps (np.ndarray): Arrays of optimized state and control values.
604+ - final_error (dict): Final state errors in x, y, psi, and c.
605+ """
606+ # def cost(p):
607+ # x_, y_, psi_, c_, v_, eps_ = np.split(p, [N - 1, 2 * (N - 1), 3 * (N - 1), 4 * (N - 1), 5 * (N - 1)])
608+ # c_seq = np.concatenate(([init_state['c']], c_))
609+ # v_seq = np.concatenate(([init_state['v']], v_))
610+ # x_final, y_final, psi_final, c_final = x_[-1], y_[-1], psi_[-1], c_[-1]
611+
612+ # cost_c = w_c * np.sum(c_seq ** 2)
613+ # cost_eps = w_eps * np.sum(eps_ ** 2)
614+ # v_mean = np.mean(v_seq)
615+ # cost_vvar = w_vvar * np.mean((v_seq - v_mean) ** 2)
616+
617+ # cost_terminal = w_terminal * (
618+ # (x_final - final_state['x']) ** 2 +
619+ # (y_final - final_state['y']) ** 2 +
620+ # (psi_final - final_state['psi']) ** 2 * 100 +
621+ # (c_final - final_state['c']) ** 2 * 100
622+ # )
623+
624+ # cost_waypoints = 0.0
625+ #
626+
627+ def cost (p ):
628+ T_total = p [- 1 ]
629+ return T_total
630+
631+ def dynamics_constraints (p ):
632+ # x_, y_, psi_, c_, v_, eps_ = np.split(p, [N - 1, 2 * (N - 1), 3 * (N - 1), 4 * (N - 1), 5 * (N - 1)])
633+ # print("x_: " +str(x_))
634+ split_idx = 5 * (N - 1 )
635+ x_ , y_ , psi_ , c_ , v_ = np .split (p [:split_idx ], 5 )
636+ # print("x_: " +str(x_))
637+
638+ a = p [split_idx :split_idx + (N - 1 )]
639+ eps = p [split_idx + (N - 1 ):- 1 ]
640+ T_total = p [- 1 ]
641+ T_k = T_total / (N - 1 )
642+
643+
644+ constraints = []
645+ x_prev , y_prev , psi_prev , c_prev , v_prev = init_state ['x' ], init_state ['y' ], init_state ['psi' ], init_state ['c' ], init_state ['v' ]
646+
647+ for k in range (N - 1 ):
648+ dx = v_prev * np .cos (psi_prev + c_prev * Lr ) * T_k
649+ dy = v_prev * np .sin (psi_prev + c_prev * Lr ) * T_k
650+ dpsi = v_prev * c_prev * T_k
651+ dv = a [k ] * T_k
652+ dc = eps [k ] * T_k
653+ constraints .extend ([
654+ x_ [k ] - (x_prev + dx ),
655+ y_ [k ] - (y_prev + dy ),
656+ psi_ [k ] - (psi_prev + dpsi ),
657+ v_ [k ] - (v_prev + dv ),
658+ c_ [k ] - (c_prev + dc )
659+ ])
660+ x_prev , y_prev , psi_prev , c_prev , v_prev = x_ [k ], y_ [k ], psi_ [k ], c_ [k ], v_ [k ]
661+ return constraints
662+
663+ def waypoint_penalty (p ):
664+ if waypoints is None or len (waypoints ) == 0 :
665+ return 0.0
666+
667+ x_ = p [:N - 1 ]
668+ y_ = p [N - 1 :2 * (N - 1 )]
669+ # Calculate equally spaced indices for each waypoint
670+ num_waypoints = len (waypoints )
671+ indices = [int ((i + 1 ) * (N - 1 ) / (num_waypoints + 1 )) for i in range (num_waypoints )]
672+ penalty = 0.0
673+ # Sum penalties for each waypoint at its corresponding index
674+ for wp_idx , waypoint in enumerate (waypoints ):
675+ traj_idx = indices [wp_idx ]
676+ penalty += waypoint_penalty_weight * (
677+ (x_ [traj_idx ] - waypoint [0 ])** 2 + (y_ [traj_idx ] - waypoint [1 ])** 2
678+ )
679+
680+ return penalty
681+
682+ # Initial guesses
683+ x_vals = np .linspace (init_state ['x' ], final_state ['x' ], N )
684+ y_vals = np .linspace (init_state ['y' ], final_state ['y' ], N )
685+ psi_vals = np .linspace (init_state ['psi' ], final_state ['psi' ], N )
686+ c_vals = np .linspace (init_state ['c' ], final_state ['c' ], N )
687+ v_vals = np .ones (N ) * init_state ['v' ]
688+
689+ a_vals = np .zeros (N - 1 )
690+ eps_vals = np .zeros (N - 1 )
691+ T_guess = 10.0
692+
693+ p0 = np .concatenate ([x_vals [1 :], y_vals [1 :], psi_vals [1 :], c_vals [1 :], v_vals [1 :],
694+ a_vals , eps_vals , [T_guess ]])
695+ num_vars = len (p0 )
696+ bounds = ([(None , None )] * (4 * (N - 1 )) +
697+ [(v_min , v_max )] * (N - 1 ) +
698+ [(a_min , a_max )] * (N - 1 ) +
699+ [(eps_min , eps_max )] * (N - 1 ) +
700+ [(T_min , T_max )]
701+ )
702+ def total_cost (p ):
703+ return cost (p ) + waypoint_penalty (p )
704+
705+ result = minimize (total_cost ,
706+ p0 ,
707+ bounds = bounds ,
708+ constraints = {'type' : 'eq' , 'fun' : dynamics_constraints },
709+ options = {'maxiter' : 1000 })
710+
711+ # print("result.x:" + str(result.x))
712+ if not result .success :
713+ raise RuntimeError ("Optimization failed" )
714+
715+
716+ split_idx = 5 * (N - 1 )
717+ x_ , y_ , psi_ , c_ , v_ = np .split (result .x [:split_idx ], 5 )
718+
719+ a = result .x [split_idx :split_idx + (N - 1 )]
720+ eps = result .x [split_idx + (N - 1 ):- 1 ]
721+ T_total = result .x [- 1 ]
722+
723+ x_full = np .concatenate (([init_state ['x' ]], x_ ))
724+ y_full = np .concatenate (([init_state ['y' ]], y_ ))
725+ psi_full = np .concatenate (([init_state ['psi' ]], psi_ ))
726+ c_full = np .concatenate (([init_state ['c' ]], c_ ))
727+ v_full = np .concatenate (([init_state ['v' ]], v_ ))
728+
729+ final_error = {
730+ 'x_error' : abs (x_full [- 1 ] - final_state ['x' ]),
731+ 'y_error' : abs (y_full [- 1 ] - final_state ['y' ]),
732+ 'psi_error' : abs (psi_full [- 1 ] - final_state ['psi' ]),
733+ 'c_error' : abs (c_full [- 1 ] - final_state ['c' ]),
734+ }
735+
736+ return x_full , y_full , psi_full , c_full , v_full , a , eps , T_total , final_error
737+ #########
738+
739+
434740def feasibility_check (trajectory , cone_map , car_width = 2.0 , safety_margin = 0.3 , v = 10.0 , Lr = 1.5 , T = 0.1 ):
435741 """
436742 Check if the car trajectory collides with any cones.
@@ -796,7 +1102,8 @@ def plan_full_slalom_trajectory(vehicle_state, cones):
7961102 current_heading = vehicle_state ['heading' ]
7971103
7981104 for cone_idx , cone in enumerate (cones ):
799- scenario , flex_wps , fixed_wp , target_heading = waypoint_generate (vehicle_state , cones , cone_idx )
1105+ # scenario, flex_wps, fixed_wp, target_heading = waypoint_generate(vehicle_state, cones, cone_idx)
1106+ scenario , flex_wps , fixed_wp , target_heading = waypoint_generate_relaxed (vehicle_state , cones , cone_idx )
8001107 if not flex_wps or fixed_wp is None :
8011108 continue
8021109 # flex_wp = flex_wps[0]
@@ -810,7 +1117,8 @@ def plan_full_slalom_trajectory(vehicle_state, cones):
8101117 'x' : fixed_wp [0 ], 'y' : fixed_wp [1 ], 'psi' : target_heading , 'c' : 0.0
8111118 }
8121119
813- x , y , psi , c , v , eps , _ = trajectory_generation (init_state , final_state , waypoints = flex_wps )
1120+ # x, y, psi, c, v, eps, _ = trajectory_generation(init_state, final_state, waypoints=flex_wps)
1121+ x , y , psi , c , v , a , eps , T_total , final_error = trajectory_generation_dynamics (init_state , final_state , waypoints = flex_wps )
8141122 x_all .extend (x )
8151123 y_all .extend (y )
8161124 v_all .extend (v )
@@ -827,22 +1135,22 @@ def plan_full_slalom_trajectory(vehicle_state, cones):
8271135
8281136 current_pos = np .array ([x [- 1 ], y [- 1 ]])
8291137
830- # # Plot overall trajectory
831- # plt.figure()
832- # plt.plot(x_all, y_all, label='Overall Trajectory')
833-
834- # # Plot cones
835- # for i, cone in enumerate(cones):
836- # plt.scatter(cone['x'], cone['y'], color='orange', s=10, label='Cone' if i == 0 else "")
837- # plt.text(cone['x'], cone['y'] + 0.5, f'C{i+1}', ha='center', fontsize=9, color='darkorange')
838-
839- # plt.title('4-Cone Full Course Trajectory')
840- # plt.xlabel('X')
841- # plt.ylabel('Y')
842- # plt.legend()
843- # plt.axis('equal')
844- # plt.grid(True)
845- # plt.show()
1138+ # Plot overall trajectory
1139+ plt .figure ()
1140+ plt .plot (x_all , y_all , label = 'Overall Trajectory' )
1141+
1142+ # Plot cones
1143+ for i , cone in enumerate (cones ):
1144+ plt .scatter (cone ['x' ], cone ['y' ], color = 'orange' , s = 10 , label = 'Cone' if i == 0 else "" )
1145+ plt .text (cone ['x' ], cone ['y' ] + 0.5 , f'C{ i + 1 } ' , ha = 'center' , fontsize = 9 , color = 'darkorange' )
1146+
1147+ plt .title ('4-Cone Full Course Trajectory' )
1148+ plt .xlabel ('X' )
1149+ plt .ylabel ('Y' )
1150+ plt .legend ()
1151+ plt .axis ('equal' )
1152+ plt .grid (True )
1153+ plt .show ()
8461154
8471155 combined_xy = [[x , y ] for x , y in zip (x_all , y_all )]
8481156 path = Path (ObjectFrameEnum .START ,combined_xy )
0 commit comments