Skip to content

Commit 543d2ee

Browse files
authored
Path Following/Pose2D - Merge pull request #20 from Mineinjava/acceleration-limiting-circle-detection
Path following is now stable, utilizing Pose2Ds
2 parents 7bf5e24 + eca939d commit 543d2ee

22 files changed

+1124
-259
lines changed

pathPlanner/PathingV2Demo.py

+292
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
from math import sqrt
2+
import random
3+
import math
4+
5+
import matplotlib.pyplot as plt
6+
7+
# PATH = [(-36, 12), (-12,-12), (12, 12), (36,-12)]
8+
PATH = [(-36, 48), (-24,36), (-12, 0), (-12,-52), (0, -52), (0, 0), (12, 0), (12, 36), (24, 48), (36, 48)]
9+
START_POSE = (-36, 48, 0)
10+
11+
# Simulation Constants
12+
LOOPTIME = 0.02 # seconds
13+
LOOPTIME_DEVIATION = 0.0 # seconds
14+
15+
MAX_VELOCITY = 62 # your unit per second
16+
MAX_ACCELERATION = 25 # your unit per second squared
17+
MAX_ANGULAR_VELOCITY = 1.0 # rad/s
18+
MAX_ANGULAR_ACCELERATION = 1.0 # rad/s^2
19+
CRUISE_VELOCITY = 30 # your unit per second
20+
ONE_FRAME_ACCEL = MAX_ACCELERATION * LOOPTIME
21+
22+
PRECISION_RADIUS = 3 # your unit
23+
SLOW_DOWN_RADIUS = CRUISE_VELOCITY / ONE_FRAME_ACCEL # your unit
24+
LOOKAHEAD = 0.0 # WARNING: BETA. YMMV
25+
26+
SLOW_DOWN_TURN = True
27+
28+
USE_SPLINE = False
29+
SPLINE_RESOLUTION = 20
30+
31+
ANIMATE = True
32+
33+
SHOW_ACCEL_VELO_GRAPH = False
34+
SHOW_GRID = True
35+
GRID_INCREMENT = 24 # your unit
36+
37+
FIELD_IMAGE = "./centerstage.png"
38+
FIELD_WIDTH = 144 # your unit
39+
FIELD_LENGTH = 144 # your unit
40+
41+
class Pose2d:
42+
def __init__(self, x, y, theta):
43+
self.x = x
44+
self.y = y
45+
self.theta = theta
46+
47+
def distance(self, other):
48+
return ((self.x - other.x) ** 2 + (
49+
self.y - other.y) ** 2) ** 0.5
50+
51+
def __sub__(self, other):
52+
return Pose2d(self.x - other.x, self.y - other.y,
53+
self.theta - other.theta)
54+
55+
def __add__(self, other):
56+
return Pose2d(self.x + other.x, self.y + other.y,
57+
self.theta + other.theta)
58+
59+
def __mul__(self, other):
60+
return Pose2d(self.x * other, self.y * other,
61+
self.theta * other)
62+
63+
def __truediv__(self, other):
64+
return Pose2d(self.x / other, self.y / other,
65+
self.theta / other)
66+
67+
def length(self):
68+
return (self.x ** 2 + self.y ** 2 + self.theta ** 2) ** 0.5
69+
70+
def __str__(self):
71+
return "x: " + str(self.x) + ", y: " + str(
72+
self.y) + ", theta: " + str(self.theta)
73+
def dot(self, other): # WARNING: BOTH POSE2DS ARE TREATES AS VECTORS WITH ORIGIN (0,0)
74+
return self.x * other.x + self.y * other.y
75+
76+
def anglesimilarity(self, other):
77+
# need to get cosine squared of half the angle between the two vectors
78+
cos = (self.dot(other)/(self.length() * other.length()))
79+
return (1 + cos) / 2
80+
81+
def lerp(a, b, t):
82+
return a + (b - a) * t
83+
84+
# https://stackoverflow.com/a/1084899/13224997
85+
def circle_intersect_line(pose, x1, y1, x2, y2):
86+
d = (x2 - x1, y2 - y1)
87+
f = (x1 - pose.x, y1 - pose.y)
88+
89+
a = d[0] ** 2 + d[1] ** 2
90+
if a == 0:
91+
return False
92+
b = 2 * (f[0] * d[0] + f[1] * d[1])
93+
c = (f[0] ** 2) + (f[1] ** 2) - (PRECISION_RADIUS ** 2)
94+
95+
discriminant = (b ** 2) - (4 * a * c)
96+
if discriminant < 0:
97+
return False
98+
else:
99+
discriminant = discriminant ** 0.5
100+
t1 = (-b - discriminant) / (2 * a)
101+
t2 = (-b + discriminant) / (2 * a)
102+
103+
return (0 <= t1 <= 1 and 0 <= t2 <= 1)
104+
105+
106+
class Waypoint(Pose2d):
107+
def __init__(self, x, y, theta):
108+
super().__init__(x, y, theta)
109+
110+
def is_hit(self, robotPose, oldRobotPose):
111+
if self.distance(robotPose) < PRECISION_RADIUS:
112+
return True
113+
elif (oldRobotPose.distance(robotPose) > self.distance(
114+
robotPose)):
115+
return circle_intersect_line(self, robotPose.x, robotPose.y,
116+
oldRobotPose.x, oldRobotPose.y)
117+
118+
def __str__(self):
119+
return super().__str__()
120+
121+
122+
class RobotPose(Pose2d):
123+
def __init__(self, x, y, theta):
124+
super().__init__(x, y, theta)
125+
126+
def __str__(self):
127+
return super().__str__() + ", velocity: "\
128+
+ ", angularVelocity: "
129+
130+
131+
if __name__ == "__main__":
132+
INITIAL_POSE = RobotPose(START_POSE[0], START_POSE[1], START_POSE[2])
133+
ipoints = PATH
134+
splinex = [point[0] for point in ipoints]
135+
spliney = [point[1] for point in ipoints]
136+
import numpy as np
137+
138+
half_width = FIELD_WIDTH/2
139+
half_length = FIELD_LENGTH/2
140+
141+
plt.style.use('dark_background')
142+
if ANIMATE:
143+
h1 = plt.plot([], [], 'rx-')
144+
plt.xlim(-half_width, half_width)
145+
plt.ylim(-half_length, half_length)
146+
if SHOW_GRID:
147+
plt.grid(True)
148+
def update_line(hl, new_data):
149+
hl.set_xdata(np.append(hl.get_xdata(), new_data[0]))
150+
hl.set_ydata(np.append(hl.get_ydata(), new_data[1]))
151+
plt.draw()
152+
plt.pause(0.01)
153+
154+
import cubicSpline
155+
if USE_SPLINE:
156+
splinex, spliney = cubicSpline.interpolate_xy(splinex, spliney, SPLINE_RESOLUTION)
157+
points = list(zip(splinex, spliney))
158+
print(points)
159+
160+
WAYPOINTS = [Waypoint(point[0], point[1], 0) for point in points]
161+
PreviousWaypoint = Waypoint(INITIAL_POSE.x, INITIAL_POSE.y, 0)
162+
robotPose = INITIAL_POSE
163+
robotPoseHistory = [robotPose]
164+
165+
velocities = []
166+
accelerations = []
167+
168+
def loop():
169+
global LOOPTIME
170+
global PreviousWaypoint
171+
drift_looptime = LOOPTIME + random.uniform(-LOOPTIME_DEVIATION,
172+
LOOPTIME_DEVIATION)
173+
global robotPose
174+
global robotPoseHistory
175+
if WAYPOINTS[0].is_hit(robotPose, robotPoseHistory[-1]):
176+
PreviousWaypoint = WAYPOINTS[0]
177+
WAYPOINTS.pop(0)
178+
if len(WAYPOINTS) == 0:
179+
return False
180+
vel = robotPose - robotPoseHistory[-1]
181+
vel /= LOOPTIME
182+
183+
robotPoseHistory.append(robotPose)
184+
185+
vec_next_wpt = WAYPOINTS[0] - robotPose
186+
187+
if len(WAYPOINTS) > 1:
188+
vec_second_wpt = WAYPOINTS[1] - WAYPOINTS[0]
189+
else:
190+
vec_second_wpt = vec_next_wpt
191+
192+
t = 1 - (vec_next_wpt.length() / PreviousWaypoint.distance(WAYPOINTS[0]))
193+
v = 1 + (LOOKAHEAD * t * ((1-t) ** 4))
194+
desiredvel = (vec_next_wpt * v) + (vec_second_wpt * (1-v))
195+
196+
if robotPose.distance(WAYPOINTS[-1]) >= SLOW_DOWN_RADIUS:
197+
desiredvel /= desiredvel.length()
198+
desiredvel *= CRUISE_VELOCITY
199+
200+
else:
201+
desiredvel /= desiredvel.length()
202+
desiredvel *= lerp(CRUISE_VELOCITY, ONE_FRAME_ACCEL, 1-(robotPose.distance(WAYPOINTS[-1]) / (SLOW_DOWN_RADIUS-PRECISION_RADIUS)))
203+
204+
# slow down on corners
205+
if (SLOW_DOWN_TURN and (len(WAYPOINTS) > 1)):
206+
if robotPose.distance(WAYPOINTS[0]) <= SLOW_DOWN_RADIUS:
207+
anglediff = vec_next_wpt.anglesimilarity(vec_second_wpt)
208+
desiredvel /= desiredvel.length()
209+
desiredvel *= lerp(CRUISE_VELOCITY, (anglediff*CRUISE_VELOCITY)+ONE_FRAME_ACCEL, 1-(robotPose.distance(WAYPOINTS[0]) / (SLOW_DOWN_RADIUS-PRECISION_RADIUS)))
210+
print(anglediff, 1-(robotPose.distance(WAYPOINTS[0]) / SLOW_DOWN_RADIUS)**2, desiredvel.length())
211+
212+
if desiredvel.length() > MAX_VELOCITY:
213+
desiredvel /= desiredvel.length()
214+
desiredvel *= MAX_VELOCITY
215+
216+
accel = (desiredvel - vel) / LOOPTIME
217+
if accel.length() > MAX_ACCELERATION:
218+
accel /= accel.length()
219+
accel *= MAX_ACCELERATION
220+
221+
accelerations.append(accel.length())
222+
223+
robotPose.velocity = vel + (accel * LOOPTIME)
224+
velocities.append(robotPose.velocity.length())
225+
robotPose += robotPose.velocity * drift_looptime
226+
if ANIMATE:
227+
update_line(h1[0], (robotPose.x, robotPose.y))
228+
return True
229+
230+
import time
231+
start = time.perf_counter()
232+
while loop():
233+
pass
234+
end = time.perf_counter()
235+
print("time", end - start, "s, average time", 1000 * (end - start) / len(robotPoseHistory), "ms")
236+
237+
238+
x = []
239+
y = []
240+
for pose in robotPoseHistory:
241+
x.append(pose.x)
242+
y.append(pose.y)
243+
244+
print("simulated time: ", len(robotPoseHistory) * LOOPTIME, "s")
245+
246+
total_dist = 0
247+
for i in range(1, len(robotPoseHistory)):
248+
distance = sqrt((robotPoseHistory[i].x - robotPoseHistory[i-1].x)**2 +
249+
(robotPoseHistory[i].y - robotPoseHistory[i-1].y)**2)
250+
total_dist += distance
251+
252+
print("Total distance: ", total_dist)
253+
254+
if SHOW_ACCEL_VELO_GRAPH:
255+
times = [i * LOOPTIME for i in range(len(velocities))]
256+
257+
plt.subplot(2, 1, 1)
258+
plt.plot(times, velocities, 'g-', label="Velocity", color="lime")
259+
plt.plot(times, accelerations, 'r-', label="Acceleration")
260+
plt.title("Acceleration and Velocity vs Time")
261+
plt.xlabel("Time (s)")
262+
plt.legend()
263+
plt.grid(SHOW_GRID)
264+
plt.subplot(2, 1, 2)
265+
266+
plt.ylim(-half_length, half_length)
267+
plt.xlim(-half_width, half_width)
268+
plt.imshow(plt.imread("./centerstage.png"), extent=[-half_length, half_length, -half_width, half_width])
269+
plt.plot([point[0] for point in ipoints],
270+
[point[1] for point in ipoints], 'bx-',
271+
color="fuchsia", label="Point to Point/Direct")
272+
plt.plot([point[0] for point in points],
273+
[point[1] for point in points], 'rx-',
274+
label="Spline")
275+
plt.plot(x, y, 'r-', color="lime", label="Loop Point/Predicted Localized Point")
276+
plt.title("Path")
277+
plt.legend()
278+
plt.ylabel("Y")
279+
plt.xlabel("X")
280+
plt.grid(SHOW_GRID, which="both", axis="both", linestyle="-", linewidth=0.5)
281+
plt.xticks(np.arange(-half_length, half_length + 1, GRID_INCREMENT))
282+
plt.yticks(np.arange(-half_width, half_width + 1, GRID_INCREMENT))
283+
284+
plt.text(-half_width, half_length * 1.1,
285+
"Simulated time: " + str(round(len(robotPoseHistory) * LOOPTIME, 2)) + "s\nTotal distance: " + str(round(total_dist, 2)) + " units",
286+
horizontalalignment='center',
287+
verticalalignment='center',
288+
bbox=dict(facecolor='white', alpha=0.5)
289+
)
290+
291+
plt.pause(0.01)
292+
plt.show()

pathPlanner/centerstage.png

127 KB
Loading

quail/src/main/java/com/mineinjava/quail/differentialSwerveModuleBase.java quail/src/main/java/com/mineinjava/quail/DifferentialSwerveModuleBase.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
/** A base class for differential swerve modules
66
*
77
*/
8-
public class differentialSwerveModuleBase extends swerveModuleBase {
8+
public class DifferentialSwerveModuleBase extends SwerveModuleBase {
99

10-
public differentialSwerveModuleBase(Vec2d position, double steeringRatio, double driveRatio) {
10+
public DifferentialSwerveModuleBase(Vec2d position, double steeringRatio, double driveRatio) {
1111
super(position, steeringRatio, driveRatio);
1212
}
1313

14-
public differentialSwerveModuleBase(Vec2d position, double steeringRatio, double driveRatio, boolean optimized) {
14+
public DifferentialSwerveModuleBase(Vec2d position, double steeringRatio, double driveRatio, boolean optimized) {
1515
super(position, steeringRatio, driveRatio, optimized);
1616
}
1717

quail/src/main/java/com/mineinjava/quail/robotMovement.java quail/src/main/java/com/mineinjava/quail/RobotMovement.java

+10-10
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,36 @@
55
/** Can represent a lot of things. Generally represents a vector (robot position, movement, etc.), plus an angle (robot rotation, robot desired rotation, etc.)
66
* Everything should be fairly self-explanatory.
77
*/
8-
public class robotMovement {
8+
public class RobotMovement {
99
public double rotation;
1010
public Vec2d translation;
1111

12-
public robotMovement(double rotation, Vec2d translation) {
12+
public RobotMovement(double rotation, Vec2d translation) {
1313
this.rotation = rotation;
1414
this.translation = translation;
1515
}
1616

17-
public robotMovement(double rotation, double translationX, double translationY) {
17+
public RobotMovement(double rotation, double translationX, double translationY) {
1818
this(rotation, new Vec2d(translationX, translationY));
1919
}
2020

21-
public robotMovement(Vec2d translation) {
21+
public RobotMovement(Vec2d translation) {
2222
this(0, translation);
2323
}
2424

25-
public robotMovement(double rotation) {
25+
public RobotMovement(double rotation) {
2626
this(rotation, 0, 0);
2727
}
2828

29-
public robotMovement() {
29+
public RobotMovement() {
3030
this(0, 0, 0);
3131
}
3232

33-
public robotMovement add(robotMovement other) {
34-
return new robotMovement(this.rotation + other.rotation, this.translation.add(other.translation));
33+
public RobotMovement add(RobotMovement other) {
34+
return new RobotMovement(this.rotation + other.rotation, this.translation.add(other.translation));
3535
}
3636

37-
public robotMovement subtract(robotMovement other) {
38-
return new robotMovement(this.rotation - other.rotation, this.translation.subtract(other.translation));
37+
public RobotMovement subtract(RobotMovement other) {
38+
return new RobotMovement(this.rotation - other.rotation, this.translation.subtract(other.translation));
3939
}
4040
}

0 commit comments

Comments
 (0)