|
9 | 9 | import matplotlib.patches as patches |
10 | 10 | import math |
11 | 11 |
|
12 | | - |
13 | | -################################################################################ |
14 | | -########## Collisiong Detection ################################################ |
15 | | -################################################################################ |
16 | | -class CollisionDetector: |
17 | | - """ |
18 | | - Simulation class to update positions of two rectangles (vehicle and pedestrian) |
19 | | - with velocities v1 and v2, performing collision detection at each time step. |
20 | | - All functions remain within the class, and variables defined in __init__ remain unchanged; |
21 | | - local copies are used during simulation. |
22 | | - """ |
23 | | - def __init__(self, x1, y1, t1, x2, y2, t2, v1, v2, total_time=10.0, desired_speed=2.0, acceleration=0.5): |
24 | | - |
25 | | - self.vehicle_x = x1 |
26 | | - self.vehicle_y = y1 |
27 | | - self.pedestrian_x = x2 |
28 | | - self.pedestrian_y = y2 |
29 | | - |
30 | | - # Vehicle parameters with buffer adjustments |
31 | | - self.vehicle_size_x = 3.2 |
32 | | - self.vehicle_size_y = 1.7 |
33 | | - self.vehicle_buffer_x = 3.0 + 1.0 |
34 | | - self.vehicle_buffer_y = 2.0 |
35 | | - |
36 | | - # Vehicle rectangle |
37 | | - self.x1 = self.vehicle_x + (self.vehicle_size_x + self.vehicle_buffer_x)*0.5 # Offset for buffer (remains constant) |
38 | | - self.y1 = self.vehicle_y |
39 | | - self.w1 = self.vehicle_size_x + self.vehicle_buffer_x # Increase width with buffer |
40 | | - self.h1 = self.vehicle_size_y + self.vehicle_buffer_y # Increase height with buffer |
41 | | - self.t1 = t1 |
42 | | - |
43 | | - # Pedestrian rectangle |
44 | | - self.x2 = x2 |
45 | | - self.y2 = y2 |
46 | | - self.w2 = 0.5 |
47 | | - self.h2 = 0.5 |
48 | | - self.t2 = t2 |
49 | | - |
50 | | - # Velocities are expected as [vx, vy] |
51 | | - self.v1 = v1 |
52 | | - self.v2 = v2 |
53 | | - |
54 | | - self.dt = 0.1 # seconds |
55 | | - self.total_time = total_time # seconds |
56 | | - |
57 | | - self.desired_speed = desired_speed |
58 | | - self.acceleration = acceleration |
59 | | - |
60 | | - def set_params(self, speed, acceleration): |
61 | | - self.desired_speed = speed |
62 | | - self.acceleration = acceleration |
63 | | - |
64 | | - def get_corners(self, x, y, w, h, theta): |
65 | | - """ |
66 | | - Returns the 4 corners of a rotated rectangle. |
67 | | - (x, y): center of rectangle |
68 | | - w, h: width and height of rectangle |
69 | | - theta: rotation angle in radians |
70 | | - """ |
71 | | - cos_t = math.cos(theta) |
72 | | - sin_t = math.sin(theta) |
73 | | - hw, hh = w / 2.0, h / 2.0 |
74 | | - |
75 | | - corners = np.array([ |
76 | | - [ hw * cos_t - hh * sin_t, hw * sin_t + hh * cos_t], |
77 | | - [-hw * cos_t - hh * sin_t, -hw * sin_t + hh * cos_t], |
78 | | - [-hw * cos_t + hh * sin_t, -hw * sin_t - hh * cos_t], |
79 | | - [ hw * cos_t + hh * sin_t, hw * sin_t - hh * cos_t] |
80 | | - ]) |
81 | | - |
82 | | - corners[:, 0] += x |
83 | | - corners[:, 1] += y |
84 | | - |
85 | | - return corners |
86 | | - |
87 | | - def get_axes(self, rect): |
88 | | - axes = [] |
89 | | - for i in range(len(rect)): |
90 | | - p1 = rect[i] |
91 | | - p2 = rect[(i + 1) % len(rect)] |
92 | | - edge = p2 - p1 |
93 | | - normal = np.array([-edge[1], edge[0]]) |
94 | | - norm = np.linalg.norm(normal) |
95 | | - if norm: |
96 | | - normal /= norm |
97 | | - axes.append(normal) |
98 | | - return axes |
99 | | - |
100 | | - def project(self, rect, axis): |
101 | | - dots = np.dot(rect, axis) |
102 | | - return np.min(dots), np.max(dots) |
103 | | - |
104 | | - def sat_collision(self, rect1, rect2): |
105 | | - """ |
106 | | - Determines if two convex polygons (rectangles) collide using the |
107 | | - Separating Axis Theorem (SAT). |
108 | | - rect1 and rect2 are numpy arrays of shape (4,2) representing their corners. |
109 | | - """ |
110 | | - axes1 = self.get_axes(rect1) |
111 | | - axes2 = self.get_axes(rect2) |
112 | | - |
113 | | - for axis in axes1 + axes2: |
114 | | - min1, max1 = self.project(rect1, axis) |
115 | | - min2, max2 = self.project(rect2, axis) |
116 | | - if max1 < min2 or max2 < min1: |
117 | | - return False |
118 | | - return True |
119 | | - |
120 | | - def plot_rectangles(self, rect1, rect2, collision, ax): |
121 | | - """ |
122 | | - Plot two rectangles on a provided axis and indicate collision by color. |
123 | | - """ |
124 | | - color = 'red' if collision else 'blue' |
125 | | - for rect in [rect1, rect2]: |
126 | | - polygon = patches.Polygon(rect, closed=True, edgecolor=color, fill=False, linewidth=2) |
127 | | - ax.add_patch(polygon) |
128 | | - ax.scatter(rect[:, 0], rect[:, 1], color=color, zorder=3) |
129 | | - ax.set_title(f"Collision: {'Yes' if collision else 'No'}") |
130 | | - |
131 | | - def run(self, is_displayed=False): |
132 | | - collision_distance = -1 |
133 | | - steps = int(self.total_time / self.dt) + 1 |
134 | | - |
135 | | - # Create local variables for positions; these will be updated |
136 | | - # without modifying the __init__ attributes. |
137 | | - current_x1 = self.x1 |
138 | | - current_y1 = self.y1 |
139 | | - current_x2 = self.x2 |
140 | | - current_y2 = self.y2 |
141 | | - current_v1 = self.v1[0] |
142 | | - |
143 | | - if is_displayed: |
144 | | - plt.ion() # Turn on interactive mode |
145 | | - fig, ax = plt.subplots(figsize=(10,5)) |
146 | | - |
147 | | - for i in range(steps): |
148 | | - t_sim = i * self.dt |
149 | | - |
150 | | - # Compute rectangle corners using the local positional variables. |
151 | | - rect1 = self.get_corners(current_x1, current_y1, self.w1, self.h1, self.t1) |
152 | | - rect2 = self.get_corners(current_x2, current_y2, self.w2, self.h2, self.t2) |
153 | | - |
154 | | - # Perform collision detection. |
155 | | - collision = self.sat_collision(rect1, rect2) |
156 | | - |
157 | | - if is_displayed: |
158 | | - # Plot the current step. |
159 | | - ax.clear() |
160 | | - self.plot_rectangles(rect1, rect2, collision, ax) |
161 | | - ax.set_aspect('equal') |
162 | | - ax.grid(True, linestyle='--', alpha=0.5) |
163 | | - ax.set_xlim(self.vehicle_x - 5, self.vehicle_x + 20) |
164 | | - ax.set_ylim(self.vehicle_y -5, self.vehicle_y +5) |
165 | | - |
166 | | - # Draw an additional rectangle (vehicle body) at the vehicle's center. |
167 | | - rect_vehiclebody = patches.Rectangle( |
168 | | - (current_x1 - (self.vehicle_size_x + self.vehicle_buffer_x)*0.5, current_y1 - self.vehicle_size_y * 0.5), |
169 | | - self.w1 - self.vehicle_buffer_x, |
170 | | - self.h1 - self.vehicle_buffer_y, |
171 | | - edgecolor='green', |
172 | | - fill=False, |
173 | | - linewidth=2, |
174 | | - linestyle='--' |
175 | | - ) |
176 | | - ax.add_patch(rect_vehiclebody) |
177 | | - |
178 | | - ax.text(0.01, 0.99, f"t = {t_sim:.1f}s", fontsize=12, transform=ax.transAxes, verticalalignment='top') |
179 | | - plt.draw() |
180 | | - |
181 | | - # Pause briefly to simulate real-time updating. |
182 | | - plt.pause(self.dt * 0.05) |
183 | | - |
184 | | - # Stop simulation if collision is detected. |
185 | | - if collision: |
186 | | - current_vehicle_x = current_x1 - (self.vehicle_size_x + self.vehicle_buffer_x) * 0.5 |
187 | | - current_vehicle_y = current_y1 |
188 | | - collision_distance = current_vehicle_x - self.vehicle_x |
189 | | - break |
190 | | - |
191 | | - # Update the vehicle's speed if it is not at the desired speed. |
192 | | - next_v = current_v1 + self.acceleration * self.dt |
193 | | - # Accelerating: Check if the vehicle is about to exceed the desired speed. |
194 | | - if next_v > self.desired_speed and current_v1 <= self.desired_speed: |
195 | | - current_v1 = self.desired_speed |
196 | | - # Decelerating: Check if the vehicle is about to fall below the desired speed. |
197 | | - elif next_v < self.desired_speed and current_v1 >= self.desired_speed: |
198 | | - current_v1 = self.desired_speed |
199 | | - else: |
200 | | - current_v1 = next_v |
201 | | - |
202 | | - current_v1 = 0.0 if current_v1 < 0.0 else current_v1 |
203 | | - |
204 | | - # Update local positions based on velocities. |
205 | | - current_x1 += current_v1 * self.dt |
206 | | - current_y1 += self.v1[1] * self.dt |
207 | | - current_x2 += self.v2[0] * self.dt |
208 | | - current_y2 += self.v2[1] * self.dt |
209 | | - |
210 | | - if is_displayed: |
211 | | - plt.ioff() |
212 | | - plt.show(block=True) |
213 | | - |
214 | | - # print("Collision distance:", collision_distance) |
215 | | - |
216 | | - return collision_distance |
217 | | - |
| 12 | +from GEMstack.onboard.planning.pedestrian_yield_logic import CollisionDetector |
218 | 13 |
|
219 | 14 | if __name__ == "__main__": |
220 | 15 | # Vehicle parameters. x, y, theta (angle in radians) |
|
0 commit comments