├── .classpath
├── .project
├── .settings
└── org.eclipse.jdt.core.prefs
├── Images
├── Image1.png
├── Image10.png
├── Image2.png
├── Image3.png
├── Image4.png
├── Image5.png
├── Image6.png
├── Image7.png
├── Image8.png
└── Image9.png
├── README.md
└── src
└── usfirst
└── frc
└── team2168
└── robot
├── FalconLinePlot.java
└── FalconPathPlanner.java
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | PathPlanner
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.jdt.core.javanature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
5 | org.eclipse.jdt.core.compiler.compliance=1.7
6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate
7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate
8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate
9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
11 | org.eclipse.jdt.core.compiler.source=1.7
12 |
--------------------------------------------------------------------------------
/Images/Image1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KHEngineering/SmoothPathPlanner/11059aa2ec314ba20b364aeea3c968aca2672b49/Images/Image1.png
--------------------------------------------------------------------------------
/Images/Image10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KHEngineering/SmoothPathPlanner/11059aa2ec314ba20b364aeea3c968aca2672b49/Images/Image10.png
--------------------------------------------------------------------------------
/Images/Image2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KHEngineering/SmoothPathPlanner/11059aa2ec314ba20b364aeea3c968aca2672b49/Images/Image2.png
--------------------------------------------------------------------------------
/Images/Image3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KHEngineering/SmoothPathPlanner/11059aa2ec314ba20b364aeea3c968aca2672b49/Images/Image3.png
--------------------------------------------------------------------------------
/Images/Image4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KHEngineering/SmoothPathPlanner/11059aa2ec314ba20b364aeea3c968aca2672b49/Images/Image4.png
--------------------------------------------------------------------------------
/Images/Image5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KHEngineering/SmoothPathPlanner/11059aa2ec314ba20b364aeea3c968aca2672b49/Images/Image5.png
--------------------------------------------------------------------------------
/Images/Image6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KHEngineering/SmoothPathPlanner/11059aa2ec314ba20b364aeea3c968aca2672b49/Images/Image6.png
--------------------------------------------------------------------------------
/Images/Image7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KHEngineering/SmoothPathPlanner/11059aa2ec314ba20b364aeea3c968aca2672b49/Images/Image7.png
--------------------------------------------------------------------------------
/Images/Image8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KHEngineering/SmoothPathPlanner/11059aa2ec314ba20b364aeea3c968aca2672b49/Images/Image8.png
--------------------------------------------------------------------------------
/Images/Image9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KHEngineering/SmoothPathPlanner/11059aa2ec314ba20b364aeea3c968aca2672b49/Images/Image9.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | SmoothPathPlanner
2 | =================
3 |
4 |
5 | After the Cheesy Poofs (FRC 254) put on an amazing display of robot path following during the 2014 FRC championship finals, a lot of buzz has been generated by other teams wishing to implement a similar feature on their robot. The poofs released their code and the community learned they used 5th order polynomial splines in order to find the best fit smooth curve from POint A to Point B.
6 |
7 | Path planning has been the primary focus in many other fields outside of FRC. For example in Game AI, generating smooths paths for the main character to follow when going around obstacles needs to be calculated in real-time. In other robotics fields such as autonomous car navigation, many smooth paths need to be generated simultaneously, and the "best" path choosen.
8 |
9 | These fields have spent many years adopting and optimizing the field of motion planning for their specific application. However, what they have in common is that the calculations for generating the path needs to be performed in real-time, and typically are performed on inexpensive embedded devices, such as gaming consoles or car computers. As part of my PhD thesis (developing a controller for an Autonomous Car) I had the opportunity to create a simplified path planner for real-time mobile robot applications.
10 |
11 | This class is a port of the code I developed for my PhD thesis, to perform the function of path calculation on a mobile, skid-steer robot very quickly. The algorithms provided here rely on algorithms used in optimal control theory to converge to a solution very quickly. The code here uses the "gradient descent" optimization technique to coerce a simple waypoint path, into a smooth trajectory.
12 |
13 | Once the smooth trajectory is determined, parallel paths for the left and right wheels are calculated based on the robots trackwidth distance, and corresponding velocities for each of the wheels are determined. As long as the robot has independant speed controllers for the left and right side of the drivetrain, the user can "play" through the velocity profile as if they are setpoints and the robot should drive the corresponding path.
14 |
15 | The function takes in just 3 paramters to get started and boost a very simple user interface.
16 |
17 | 1. `TotalTime` - is the time you wish the robot to complete the path, this is used to calculate the velocity
18 | 2. `Time Step` - this is the rate at which the controller on your robot runs. Typically for FRC we run our controllers at a 100ms rate in a separate thread from the main Robot.
19 | 3. `TrackWidth` - the distance between the left and right wheels of the robot. This is used to calculate the left and right wheel paths.
20 |
21 | The waypoints are simply global positions you would like your robot to drive, specified as a 2D array. The order of the points specify the path which the user wishes to drive, and the smooth path is generated accordingly. The smooth path generated also uses the Max Time, and controller periodic rate to automatically create the proper length velocity array so the user simply steps through each element once every time step. The end result is that the robot drives along the path, depending how well your speed controllers are tuned. So for example, if you have a controller running at 100ms, and you want a path that will take no more than 5 seconds to travsers, the algorithm will provide the left and right velocities as arrays with no more than 5/0.1 = 50 points. Each point will contain the velocity the robot should be at the time to make the end position by the end of travel.
22 |
23 | Here is an an example to generate a path using the default paramters.
24 |
25 | ```java
26 | //create waypoint path
27 | double[][] waypoints = new double[][]{
28 | {1, 1},
29 | {5, 1},
30 | {9, 12},
31 | {12, 9},
32 | {15, 6},
33 | {19, 12}
34 | };
35 |
36 | double totalTime = 8; //max seconds we want to drive the path
37 | double timeStep = 0.1; //period of control loop on Rio, seconds
38 | double robotTrackWidth = 2; //distance between left and right wheels, feet
39 |
40 | FalconPathPlanner path = new FalconPathPlanner(waypoints);
41 | path.calculate(totalTime, timeStep, robotTrackWidth);
42 | ```
43 |
44 | After the `calculate` method is called, the smooth path and left and right velocities are all calculated and stored into the class data members. Below is the list of each class path variable. Note, all of the data members of this class are public. While this is usually not a good idea, I think for this case, and how we use these classes in FRC, it makes sense to leave these as public so the user can access them and modify them as needed. Note `path` is the class instantiated from the example above.
45 |
46 | ```Java
47 | path.origPath; // 2D array of doubles which contains the original waypoint path provided
48 | path.nodeOnlyPath; // 2D array of doubles which filters the original path and only keeps points which
49 | //cause the path to change direction (i.e. all intermediate points on a straight line
50 | //are removed)
51 | path.smoothPath; //2D array of doubles represents the smooth path for the center of the robot.
52 | path.leftPath; //2D array of doubles represents the left wheel path (based on track width of robot)
53 | path.rightPath; //2D array of doubles represents the right wheel path (based on track width of robot)
54 |
55 | path.smoothCenterVelocity; //2D array of doubles which contains a smooth velocity profile of the smoothPath above
56 | path.smoothRightVelocity; //2D array of doubles which contains a smooth velocity profile of the rightPath above
57 | path.smoothLeftVelocity; //2D array of doubles which contains a smooth velocity profile of the leftPath above
58 | ```
59 |
60 | At this point all you need to do is pass `path.smoothRightVelocity` and `path.smoothLeftVelocity` to the Robots left and right wheel speed controllers. "Play" through the array updating the setpoint of the controller with the next element of the array each time step.
61 |
62 | >Note: ALL PATH 2 DIMENSIONAL ARRAYS CONTAIN THE X-COORDINATE IN THE FIRST DIMENSION, AND THE Y COODINATE IN THE SECOND DIMENSION. SO `path.rightPath[i][0]` would be the ith X- coodinate, and `path.rightPath[i][1]` would be the ith Y- coordinate of the right wheel path for example. THE UNITS OF THESE ARE THE GLOBAL UNITS ASSUMED BY THE USER.
63 |
64 | >Note: ALL VELOCITY 2 DIMENSIONAL ARRAYS CONTAIN THE TIME VECOTOR IN THE FIRST DIMENSION, AND THE VELOCITY MAGNITUDE IN THE SECOND DIMENSION. So `path.smoothLeftVelocity[i][0]` would be the ith time step value in seconds, and `path.smoothLeftVelocity[i][1]` would be the ith velocity magnitude in units per second. **When attemping to command the speed controller, the user only needs to pass the `path.smoothLeftVelocity[i][1] array, which is a single dimension array of doubles to the speed controller.**
65 |
66 | Furthermore, the algorithm also implements a smooth start/stop routine and automatically ramps up and down to a zero velocity so at the end of travel the robot slows down to zero. The coerceion to zero velocity takes in to account the total distance needed to travel and modifies the velocity accordingly.
67 |
68 | Typically when I write complex algorithms, I tend to put the Big O notation in the JavaDoc comments of each method and I also write down a pseudocode to help guide me as I program the algorithm. Typically I remove these comments in the final product, but I decided to leave them in to help people follow my logic, and see which Algorithms I have optimized, and which algorithms could use more optimization. Most algorithms have been optimized to linear Big O notation.
69 |
70 | I have tested this algorithm on a 2015 RoboRio and for a path containing
71 |
72 |
73 |
74 | ## Using the Included Grapher
75 |
76 | I have also included a very crude graphing class to help visualize the output. The graphing class is also written using native Java SE classes and will run on any desktop.
77 |
78 | >Note: Because the RoboRio is headless (meaning no display), the Graph functions can not run on the RoboRio. Check `if(!GraphicsEnvironment.isHeadless())` before graphing this way the code is completely portable and will run on both desktop and RoboRio.
79 |
80 | So let’s work through an example of how to use this class:
81 |
82 | If we want to graph the output of the path above we can do the following and produce the following plot:
83 | ```Java
84 | FalconLinePlot fig1 = new FalconLinePlot(path.nodeOnlyPath,Color.blue,Color.green);
85 | ```
86 |
87 | 
88 |
89 | The plot above isn’t that helpful. However we can configure it to be more useful to us. Since this is the plot of the global path of the robot. Let’s change the X- and Y- axis to reflect the dimensions of half of the FRC field (24 x 27ft) and lets add some labels and grid lines.
90 |
91 | ```Java
92 | fig1.yGridOn();
93 | fig1.xGridOn();
94 | fig1.setYLabel("Y (feet)");
95 | fig1.setXLabel("X (feet)");
96 | fig1.setTitle("Top Down View of FRC Field (24ft x 27ft) \n shows global position of robot path, along with left and right wheel trajectories");
97 |
98 | //force graph to show 1/2 field dimensions of 24ft x 27 feet
99 | fig1.setXTic(0, 27, 1);
100 | fig1.setYTic(0, 24, 1);
101 | ```
102 | 
103 |
104 | The above figure is more useful. It shows the general path we wish the robot to take. Starting at our waypoint {1,1} and ending at {19,12}. This plot is in feet. The real usefulness of the plotter function is the ability to add additional plots to one figure. You can add an unlimited amount of charts using the `addData` method. There are also options to specify line color, and marker color. If you do not wish to have lines or markers, set them to `null`.
105 |
106 | Lets add the smooth center path the algorithm calculated to this plot. Which gives the following:
107 | ```Java
108 | fig1.addData(path.smoothPath, Color.red, Color.blue);
109 | ```
110 | 
111 | We can see the smooth path algorithm does two things. It creates many more data points and provides smooth transitions around the corners. The number of data points created is based on the max Time you wish the robot to complete the path, and the time step of the controller.
112 |
113 | We can also add the left and right path trajectories to the plot to provide all the useful information we need in one chart.
114 |
115 | ```Java
116 | fig1.addData(path.leftPath, Color.magenta);
117 | fig1.addData(path.rightPath, Color.magenta);
118 | ```
119 |
120 | 
121 |
122 | I choose this example for a reason, and we can see some tight turns on the robot in the example. (Maybe too tight) We will fix those later (todo: see section here), but first let’s see what the velocity looks like for this path.
123 |
124 | ```Java
125 | FalconLinePlot fig2 = new FalconLinePlot(path.smoothCenterVelocity,null,Color.blue);
126 | fig2.yGridOn();
127 | fig2.xGridOn();
128 | fig2.setYLabel("Velocity (ft/sec)");
129 | fig2.setXLabel("time (seconds)");
130 | fig2.setTitle("Velocity Profile for Left and Right Wheels \n Left = Cyan, Right = Magenta");
131 | fig2.addData(path.smoothRightVelocity, Color.magenta);
132 | fig2.addData(path.smoothLeftVelocity, Color.cyan);
133 | ```
134 | 
135 |
136 |
137 | This is where you need to determine, if the velocities shown can be accomplished by your robot. Looks like the max and min velocity is achievable for most drivetrains so let’s continue. We also note there is a relatively smooth transition between data points. If your velocity isn’t smooth, modify the way points so that it is smooth. The robot will not react well to instant changes in velocity. Note the total time is 7.2 seconds which is less than the `8` second requirement we provided. If we increase or decrease the time requirement, the magnitude of the velocity will decrease or increase, respectively.
138 |
139 | ## Let’s recreate the Cheezy Poof Path
140 | As another example we are going to try and replicate the smooth path used by FRC Team 254 during the 2014 FRC Einstein Finals, Match 3.
141 |
142 | ```Java
143 | //Lets create a bank image
144 | FalconLinePlot fig3 = new FalconLinePlot(new double[][]{{0.0,0.0}});
145 | fig3.yGridOn();
146 | fig3.xGridOn();
147 | fig3.setYLabel("Y (feet)");
148 | fig3.setXLabel("X (feet)");
149 | fig3.setTitle("Top Down View of FRC Field (28ft x 27ft) \n shows global position of robot path, along with left and right wheel trajectories");
150 |
151 | //force graph to show 1/2 field dimensions of 24.8ft x 27 feet
152 | double fieldWidth = 32.0;
153 | fig3.setXTic(0, 27, 1);
154 | fig3.setYTic(0, fieldWidth, 1);
155 | ```
156 |
157 | 
158 |
159 |
160 | We can add the Goal wall, and low goals to the view.
161 |
162 | ```Java
163 | //lets add field markers to help visual
164 | //http://www.usfirst.org/sites/default/files/uploadedFiles/Robotics_Programs/FRC/Game_and_Season__Info/2014/fe-00037_RevB.pdf
165 | //Goal line
166 | double[][] goalLine = new double[][] {{26.5,0}, {26.5, fieldWidth}};
167 | fig3.addData(goalLine, Color.black);
168 |
169 | //Low Goals roughly 33 inch x 33 inch and 24.6 ft apart (inside to inside)
170 | double[][] leftLowGoal = new double[][]{
171 | {26.5, fieldWidth/2 + 24.6/2},
172 | {26.5, (fieldWidth)/2 + 24.6/2 + 2.75},
173 | {26.5 - 2.75, fieldWidth/2 + 24.6/2 + 2.75},
174 | {26.5 - 2.75, fieldWidth/2 + 24.6/2},
175 | {26.5, fieldWidth/2 + 24.6/2},
176 | };
177 |
178 | double[][] rightLowGoal = new double[][]{
179 | {26.5, fieldWidth/2 - 24.6/2},
180 | {26.5, fieldWidth/2 - 24.6/2 - 2.75},
181 | {26.5 - 2.75, fieldWidth/2 - 24.6/2 - 2.75},
182 | {26.5 - 2.75, fieldWidth/2 - 24.6/2},
183 | {26.5, fieldWidth/2 - 24.6/2},
184 | };
185 |
186 | fig3.addData(leftLowGoal, Color.black);
187 | fig3.addData(rightLowGoal, Color.black);
188 |
189 | //Auto Line
190 | double[][] autoLine = new double[][] {{26.5-18,0}, {26.5-18, fieldWidth}};
191 | fig3.addData(autoLine, Color.black);
192 | ```
193 | 
194 |
195 | Using the above plot, we can create the following waypoint trajectory to replicate the path taken by FRC 254 on Einstein Final Match 3;
196 |
197 | ```Java
198 | double[][] CheesyPath = new double[][]{
199 | {7,16},
200 | {11,16},
201 | {17,28},
202 | {22,28},
203 | };
204 |
205 | double totalTime = 4.5; //seconds
206 | double timeStep = 0.1; //period of control loop on Rio, seconds
207 | double robotTrackWidth = 2; //distance between left and right wheels, feet
208 |
209 | final FalconPathPlanner path = new FalconPathPlanner(CheesyPath);
210 | path.calculate(totalTime, timeStep, robotTrackWidth);
211 |
212 | fig3.addData(path.nodeOnlyPath,Color.blue,Color.green);
213 | ```
214 | 
215 |
216 |
217 | Adding all of the other path parameters as well:
218 |
219 | ```Java
220 | //add all other paths
221 | fig3.addData(path.smoothPath, Color.red, Color.blue);
222 | fig3.addData(path.leftPath, Color.magenta);
223 | fig3.addData(path.rightPath, Color.magenta);
224 | ```
225 | 
226 |
227 |
228 | We also plot the Velocity of this chart:
229 | ```Java
230 | //Velocity
231 | FalconLinePlot fig4 = new FalconLinePlot(path.smoothCenterVelocity,null,Color.blue);
232 | fig4.yGridOn();
233 | fig4.xGridOn();
234 | fig4.setYLabel("Velocity (ft/sec)");
235 | fig4.setXLabel("time (seconds)");
236 | fig4.setTitle("Velocity Profile for Left and Right Wheels \n Left = Cyan, Right = Magenta");
237 | fig4.addData(path.smoothRightVelocity, Color.magenta);
238 | fig4.addData(path.smoothLeftVelocity, Color.cyan);
239 | ```
240 |
241 | 
242 |
243 |
244 | This is actually a really nice velocity curve. The transactions are very smooth for each wheel. Assuming our robot can meet these velocities we will traverse the smooth path in 4.2 seconds and stop! You will notice the left wheel slows down first to allow for the slight left turn in the beginning, and then the right wheel slows down to allow for the slight right turn.
245 |
246 | At this point, all we need to do is pass `path.smoothLeftVelocity[i][1]` and ``path.smoothRightVelocity[i][1]` to our robot Left and right wheel speed controllers, and update the setpoint to the next value in the array each robot loop iteration. The robot will “play” through the array and drive the smooth path.
247 |
248 | The complete code for this example is below:
249 |
250 | ```Java
251 | /***Poof Example***/
252 |
253 | //Lets create a bank image
254 | FalconLinePlot fig3 = new FalconLinePlot(new double[][]{{0.0,0.0}});
255 | fig3.yGridOn();
256 | fig3.xGridOn();
257 | fig3.setYLabel("Y (feet)");
258 | fig3.setXLabel("X (feet)");
259 | fig3.setTitle("Top Down View of FRC Field (30ft x 27ft) \n shows global position of robot path, along with left and right wheel trajectories");
260 |
261 |
262 | //force graph to show 1/2 field dimensions of 24.8ft x 27 feet
263 | double fieldWidth = 32.0;
264 | fig3.setXTic(0, 27, 1);
265 | fig3.setYTic(0, fieldWidth, 1);
266 |
267 |
268 | //lets add field markers to help visual
269 | //http://www.usfirst.org/sites/default/files/uploadedFiles/Robotics_Programs/FRC/Game_and_Season__Info/2014/fe-00037_RevB.pdf
270 | //Goal line
271 | double[][] goalLine = new double[][] {{26.5,0}, {26.5, fieldWidth}};
272 | fig3.addData(goalLine, Color.black);
273 |
274 | //Low Goals roughly 33 inch x 33 inch and 24.6 ft apart (inside to inside)
275 | double[][] leftLowGoal = new double[][]{
276 | {26.5, fieldWidth/2 + 24.6/2},
277 | {26.5, (fieldWidth)/2 + 24.6/2 + 2.75},
278 | {26.5 - 2.75, fieldWidth/2 + 24.6/2 + 2.75},
279 | {26.5 - 2.75, fieldWidth/2 + 24.6/2},
280 | {26.5, fieldWidth/2 + 24.6/2},
281 | };
282 |
283 | double[][] rightLowGoal = new double[][]{
284 | {26.5, fieldWidth/2 - 24.6/2},
285 | {26.5, fieldWidth/2 - 24.6/2 - 2.75},
286 | {26.5 - 2.75, fieldWidth/2 - 24.6/2 - 2.75},
287 | {26.5 - 2.75, fieldWidth/2 - 24.6/2},
288 | {26.5, fieldWidth/2 - 24.6/2},
289 | };
290 |
291 | fig3.addData(leftLowGoal, Color.black);
292 | fig3.addData(rightLowGoal, Color.black);
293 |
294 | //Auto Line
295 | double[][] autoLine = new double[][] {{26.5-18,0}, {26.5-18, fieldWidth}};
296 | fig3.addData(autoLine, Color.black);
297 |
298 |
299 | double[][] CheesyPath = new double[][]{
300 | {7,16},
301 | {11,16},
302 | {17,28},
303 | {22,28},
304 | };
305 |
306 | double totalTime = 4.5; //seconds
307 | double timeStep = 0.1; //period of control loop on Rio, seconds
308 | double robotTrackWidth = 2; //distance between left and right wheels, feet
309 |
310 | final FalconPathPlanner path = new FalconPathPlanner(CheesyPath);
311 | path.calculate(totalTime, timeStep, robotTrackWidth);
312 |
313 | //waypoint path
314 | fig3.addData(path.nodeOnlyPath,Color.blue,Color.green);
315 |
316 | //add all other paths
317 | fig3.addData(path.smoothPath, Color.red, Color.blue);
318 | fig3.addData(path.leftPath, Color.magenta);
319 | fig3.addData(path.rightPath, Color.magenta);
320 |
321 |
322 | //Velocity
323 | FalconLinePlot fig4 = new FalconLinePlot(path.smoothCenterVelocity,null,Color.blue);
324 | fig4.yGridOn();
325 | fig4.xGridOn();
326 | fig4.setYLabel("Velocity (ft/sec)");
327 | fig4.setXLabel("time (seconds)");
328 | fig4.setTitle("Velocity Profile for Left and Right Wheels \n Left = Cyan, Right = Magenta");
329 | fig4.addData(path.smoothRightVelocity, Color.magenta);
330 | fig4.addData(path.smoothLeftVelocity, Color.cyan);
331 |
332 | }
333 | ```
334 |
335 |
336 |
337 | Modifying the calculation paramters
338 | =================
339 | The "smoothness" of the path is controlled by two paramters: Alpha and Beta. These variables take on default values which I believe will work in most cases and do not need to be modified. However, this section covers how to modify these paramters so that you can generate the exact path you wish to traverse.
340 |
341 | You can change these paramters individually by calling the setter methods.
342 |
343 | ```Java
344 | path.setPathAlpha(0.5);
345 | path.setPathBeta(0.9);
346 | ```
347 |
348 | After doing-so you will need to call the recalculate menthod to re-calculate the paths using the new paramters
349 | ```Java
350 | path.calculate(totalTime, timeStep, robotTrackWidth);
351 | ```
352 |
353 | Alpha controls how close to the original line we wish the smooth points to be. Beta controls how curvy we wish our path be. The closer Alpha is to 1, the closer the smooth path will be to the original line, the closer Beta is to 1, the larger the path radii should be. Note, these are conflicting requirements, and thus why it is an optimization algorithm. You can think of it this way, as Alpha increases it wants to push the points toward the original path, as beta increses it want to push the points away from the original line and there is a constant tug of war between them. The algorithm converges when both are happy with where the points get pushed.
354 |
355 | Alpha and Beta should be a decimal between 0 and 1. If Alpha is zero regarless of Beta, the output will be a straight line from the starting waypoint to the ending waypoint.
356 |
357 | TODO: add image of different params
358 |
359 | Caution: Depending on the combination of the variables, it is very possible to set the parameters such that the algorithm never convergers! So be careful. In general I have found the following settings to be acceptable for converging. However, they are not guranteed and you should test many datasets to be sure, the algorithm will convege to a solution in all your scenarios. If you decide to change the paramters from their defaults.
360 |
361 | If the algorithm does not converge, the program will simply never finish, so it is pretty easy to identify.
362 |
363 | In genral if you must change the parameters Set Beta between 0.1 and 0.4 (Do not set to zero), and set Alpha between 0 and 1 to get desired path output, but here is a complete list of recommended paramters.
364 |
365 | | Alpha | Beta |
366 | | ------------- |-------------: |
367 | | Any val from 0 to 1 | 0.0 |
368 | | Any val from 0 to 1 | 0.1 |
369 | | Any val from 0 to 1 | 0.2 |
370 | | Any val from 0 to 1 | 0.3 |
371 | | Any val from 0 to 1 | 0.4 |
372 | | Any val from 0 to 0.9 | 0.5 |
373 | | Any val from 0 to 0.7 | 0.6 |
374 | | Any val from 0 to 0.5 | 0.7 |
375 | | Any val from 0 to 0.3 | 0.8 |
376 | | Any val from 0 to 0.1 | 0.9 |
377 | | None | 1.0 |
378 |
379 |
380 |
381 |
382 | Real-Time Course Correction
383 | ============================
384 | The algorithm also provides what the robots heading should be at each time stamp, based on the global coordinate system. The user can use these headings to correct the robots course. As long as the robot maintains the appropriate headings and velocities of each wheels, the robot will drive the course outlined.
385 |
386 | The current implementation of the heading is accumulated, not absolute. This is so that users can use the Gyro class already included with WPI lib (which is also accumulated) as a sensor to detect the robots actual heading and course correct.
387 |
388 | the heading information is stored in member variable `heading`, so to access it one could call
389 |
390 | ```java
391 | path.heading[i][0]; //ith time vector for path heading
392 | path.heading[i][1]; //ith point heading in degrees
393 | ```
394 |
395 | > Note: This algorithm doesn't consider pose of the robot. What that means is it doesn't require an initial or final orientation of the robot, and does not gurantee the final orientation. Once you reach the desintation, you may need to then command your chassis to rotate to the desired final heading.
396 |
397 |
398 | Assumptions
399 | =========================
400 | 1. Robot Chassis is a skid-steer platform
401 | 2. Ground robot is to travel on is considered to be an even plane, with no hills, ramps, bumbs, or other obstacles
402 | 3. Robot has independant speed controllers for both sides of the Robot Chassis, tuned to meet the velocity profile changes
403 | 4. Sufficient time is given for the robot to complete the path.
404 |
--------------------------------------------------------------------------------
/src/usfirst/frc/team2168/robot/FalconLinePlot.java:
--------------------------------------------------------------------------------
1 | package usfirst.frc.team2168.robot;
2 |
3 | import java.awt.*;
4 | import java.awt.datatransfer.Clipboard;
5 | import java.awt.datatransfer.ClipboardOwner;
6 | import java.awt.datatransfer.DataFlavor;
7 | import java.awt.datatransfer.Transferable;
8 | import java.awt.datatransfer.UnsupportedFlavorException;
9 | import java.awt.event.ActionEvent;
10 | import java.awt.event.ActionListener;
11 | import java.awt.event.MouseAdapter;
12 | import java.awt.event.MouseEvent;
13 | import java.awt.geom.*;
14 | import java.awt.image.BufferedImage;
15 | import java.io.IOException;
16 | import java.text.DecimalFormat;
17 | import java.util.LinkedList;
18 |
19 | import javax.swing.*;
20 |
21 | /**
22 | * This class is a basic plotting class using the Java AWT interface. It has basic features which allow the user
23 | * to plot multiple graphs on one figure, control axis dimensions, and specify colors.
24 | *
25 | * This is by all means not an extensive plotter, but it will help visualize data very quickly and accurately. If
26 | * a more robust plotting function is required, the user is encouraged to use Excel or Matlab. The purpose of this
27 | * class is to be easy to use with enought automation to have nice graphs with minimal effort, but give the user
28 | * control over as much as possible, so they can generate the perfect chart.
29 | *
30 | * The plotter also features the ability to capture screen shots directly from the right-click menu, this allows
31 | * the user to copy and paste plots into reports or other documents rather quickly.
32 | *
33 | * This class holds an interface similar to that of Matlab.
34 | *
35 | * This class currently only supports scatterd line charts.
36 | *
37 | * @author Kevin Harrilal
38 | * @email kevin@team2168.org
39 | * @version 1
40 | * @date 9 Sept 2014
41 | *
42 | */
43 |
44 | class FalconLinePlot extends JPanel implements ClipboardOwner{
45 |
46 |
47 | private static final long serialVersionUID = 3205256608145459434L;
48 | private final int yPAD = 60; //controls how far the X- and Y- axis lines are away from the window edge
49 | private final int xPAD = 70; //controls how far the X- and Y- axis lines are away from the window edge
50 |
51 | private double upperXtic;
52 | private double lowerXtic;
53 | private double upperYtic;
54 | private double lowerYtic;
55 | private boolean yGrid;
56 | private boolean xGrid;
57 |
58 | private double yMax;
59 | private double yMin;
60 | private double xMax;
61 | private double xMin;
62 |
63 | private int yticCount;
64 | private int xticCount;
65 | private double xTicStepSize;
66 | private double yTicStepSize;
67 |
68 | boolean userSetYTic;
69 | boolean userSetXTic;
70 |
71 | private String xLabel;
72 | private String yLabel;
73 | private String titleLabel;
74 | protected static int count = 0;
75 |
76 | JPopupMenu menu = new JPopupMenu("Popup");
77 |
78 | //Link List to hold all different plots on one graph.
79 | private LinkedList link;
80 |
81 |
82 | /**
83 | * Constructor which Plots only Y-axis data.
84 | * @param yData is a array of doubles representing the Y-axis values of the data to be plotted.
85 | */
86 | public FalconLinePlot(double[] yData)
87 | {
88 | this(null,yData,Color.red);
89 | }
90 |
91 | public FalconLinePlot(double[] yData,Color lineColor, Color marker)
92 | {
93 | this(null,yData,lineColor,marker);
94 | }
95 |
96 | /**
97 | * Constructor which Plots chart based on provided x and y data. X and Y arrays must be of the same length.
98 | * @param xData is an array of doubles representing the X-axis values of the data to be plotted.
99 | * @param yData is an array of double representing the Y-axis values of the data to be plotted.
100 | */
101 | public FalconLinePlot(double[] xData, double[] yData)
102 | {
103 | this(xData,yData,Color.red,null);
104 | }
105 |
106 | /**
107 | * Constructor which Plots chart based on provided x and y axis data.
108 | * @param data is a 2D array of doubles of size Nx2 or 2xN. The plot assumes X is the first dimension, and y data
109 | * is the second dimension.
110 | */
111 | public FalconLinePlot(double[][] data)
112 | {
113 | this(getXVector(data),getYVector(data),Color.red,null);
114 | }
115 |
116 | /**
117 | * Constructor which plots charts based on provided x and y axis data in a single two dimensional array.
118 | * @param data is a 2D array of doubles of size Nx2 or 2xN. The plot assumes X is the first dimension, and y data
119 | * is the second dimension.
120 | * @param lineColor is the color the user wishes to be displayed for the line connecting each datapoint
121 | * @param markerColor is the color the user which to be used for the data point. Make this null if the user wishes to
122 | * not have datapoint markers.
123 | */
124 | public FalconLinePlot(double[][] data, Color lineColor, Color markerColor)
125 | {
126 | this(getXVector(data),getYVector(data),lineColor,markerColor);
127 | }
128 |
129 | /**
130 | * Constructor which plots charts based on provided x and y axis data provided as separate arrays. The user can also specify the color of the adjoining line.
131 | * Data markers are not displayed.
132 | * @param xData is an array of doubles representing the X-axis values of the data to be plotted.
133 | * @param yData is an array of double representing the Y-axis values of the data to be plotted.
134 | * @param lineColor is the color the user wishes to be displayed for the line connecting each datapoint
135 | */
136 | public FalconLinePlot(double[] xData, double[] yData,Color lineColor)
137 | {
138 | this(xData,yData,lineColor,null);
139 | }
140 |
141 |
142 |
143 | /**
144 | * Constructor which plots charts based on provided x and y axis data, provided as separate arrays. The user
145 | * can also specify the color of the adjoining line and the color of the datapoint maker.
146 | * @param xData is an array of doubles representing the X-axis values of the data to be plotted.
147 | * @param yData is an array of double representing the Y-axis values of the data to be plotted.
148 | * @param lineColor is the color the user wishes to be displayed for the line connecting each datapoint
149 | * @param markerColor is the color the user which to be used for the data point. Make this null if the user wishes to
150 | * not have datapoint markers.
151 | */
152 | public FalconLinePlot(double[] xData, double[] yData,Color lineColor, Color markerColor)
153 | {
154 | xLabel = "X axis";
155 | yLabel = "Y axis";
156 | titleLabel = "Title";
157 |
158 | upperXtic = -Double.MAX_VALUE;
159 | lowerXtic = Double.MAX_VALUE;
160 | upperYtic = -Double.MAX_VALUE;
161 | lowerYtic = Double.MAX_VALUE;
162 | xticCount = -Integer.MAX_VALUE;
163 |
164 | this.userSetXTic = false;
165 | this.userSetYTic = false;
166 |
167 | link = new LinkedList();
168 |
169 | addData(xData, yData, lineColor,markerColor);
170 |
171 | count ++;
172 | JFrame g = new JFrame("Figure " + count);
173 | g.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
174 | g.add(this);
175 | g.setSize(600,400);
176 | g.setLocationByPlatform(true);
177 | g.setVisible(true);
178 |
179 | menu(g,this);
180 | }
181 |
182 | /**
183 | * Adds a plot to an existing figure.
184 | * @param yData is a array of doubles representing the Y-axis values of the data to be plotted.
185 | * @param color is the color the user wishes to be displayed for the line connecting each datapoint
186 | */
187 |
188 | public void addData(double[] y, Color lineColor)
189 | {
190 | addData(y, lineColor, null);
191 | }
192 |
193 | public void addData(double[] y, Color lineColor, Color marker)
194 | {
195 | //cant add y only data unless all other data is y only data
196 | for(xyNode data: link)
197 | if(data.x != null)
198 | throw new Error ("All previous chart series need to have only Y data arrays");
199 |
200 | addData(null,y,lineColor, marker);
201 | }
202 |
203 | public void addData(double[] x, double[] y, Color lineColor)
204 | {
205 | addData(x,y,lineColor,null);
206 | }
207 |
208 |
209 | public void addData(double[][] data, Color lineColor)
210 | {
211 | addData(getXVector(data),getYVector(data),lineColor,null);
212 | }
213 |
214 | public void addData(double[][] data, Color lineColor, Color marker)
215 | {
216 | addData(getXVector(data),getYVector(data),lineColor,marker);
217 | }
218 |
219 | public void addData(double[] x, double[] y, Color lineColor, Color marker)
220 | {
221 | xyNode Data = new xyNode();
222 |
223 | //copy y array into node
224 | Data.y = new double[y.length];
225 | Data.lineColor = lineColor;
226 |
227 | if(marker == null)
228 | Data.lineMarker = false;
229 | else
230 | {
231 | Data.lineMarker = true;
232 | Data.markerColor = marker;
233 | }
234 | for(int i=0; i list)
368 | {
369 | for(xyNode node: list)
370 | {
371 | double tempYMax = getMax(node.y);
372 | double tempYMin = getMin(node.y);
373 |
374 | if(tempYMinyMax)
378 | yMax=tempYMax;
379 |
380 | if(xticCount < node.y.length)
381 | xticCount = node.y.length;
382 |
383 |
384 | if(node.x != null)
385 | {
386 | double tempXMax = getMax(node.x);
387 | double tempXMin = getMin(node.x);
388 |
389 | if(tempXMinxMax)
393 | xMax=tempXMax;
394 |
395 | }
396 | else
397 | {
398 | xMax=node.y.length-1;
399 | xMin=0;
400 |
401 | }
402 |
403 | }
404 |
405 | }
406 |
407 | private double getMax(double[] data) {
408 | double max = -Double.MAX_VALUE;
409 | for(int i = 0; i < data.length; i++) {
410 | if(data[i] > max)
411 | max = data[i];
412 | }
413 | return max;
414 | }
415 |
416 | private double getMin(double[] data) {
417 | double min = Double.MAX_VALUE;
418 | for(int i = 0; i < data.length; i++) {
419 | if(data[i] < min)
420 | min = data[i];
421 | }
422 | return min;
423 | }
424 |
425 | public void setYLabel(String s)
426 | {
427 | yLabel = s;
428 | }
429 |
430 | public void setXLabel(String s)
431 | {
432 | xLabel = s;
433 | }
434 |
435 | public void setTitle(String s)
436 | {
437 | titleLabel = s;
438 | }
439 |
440 | private void setYLabel(Graphics2D g2, String s)
441 | {
442 | FontMetrics fm = getFontMetrics(getFont());
443 | int width = fm.stringWidth(s);
444 |
445 | AffineTransform temp = g2.getTransform();
446 |
447 | AffineTransform at = new AffineTransform();
448 | at.setToRotation(-Math.PI /2, 10, getHeight()/2+width/2);
449 | g2.setTransform(at);
450 |
451 | //draw string in center of y axis
452 | g2.drawString(s, 10, 7+getHeight()/2+width/2);
453 |
454 | g2.setTransform(temp);
455 |
456 | }
457 |
458 | private void setXLabel(Graphics2D g2, String s)
459 | {
460 | FontMetrics fm = getFontMetrics(getFont());
461 | int width = fm.stringWidth(s);
462 |
463 | g2.drawString(s, getWidth()/2-(width/2), getHeight()-10);
464 | }
465 |
466 | private void setTitle(Graphics2D g2, String s)
467 | {
468 | FontMetrics fm = getFontMetrics(getFont());
469 |
470 | String[] line = s.split("\n");
471 |
472 | int height = xPAD/2 - ((line.length-1) * fm.getHeight()/2);
473 |
474 | for (int i=0; i {5,1} -> {9,12} -> {12, 9} -> {15,6} -> {15,4}
87 |
88 | The units of these coordinates are position units assumed by the user (i.e inch, foot, meters)
89 | * @param path
90 | */
91 | public FalconPathPlanner(double[][] path)
92 | {
93 | this.origPath = doubleArrayCopy(path);
94 |
95 | //default values DO NOT MODIFY;
96 | pathAlpha = 0.7;
97 | pathBeta = 0.3;
98 | pathTolerance = 0.0000001;
99 |
100 | velocityAlpha = 0.1;
101 | velocityBeta = 0.3;
102 | velocityTolerance = 0.0000001;
103 | }
104 |
105 | public static void print(double[] path)
106 | {
107 | System.out.println("X: \t Y:");
108 |
109 | for(double u: path)
110 | System.out.println(u);
111 | }
112 |
113 |
114 |
115 | /**
116 | * Prints Cartesian Coordinates to the System Output as Column Vectors in the Form X Y
117 | * @param path
118 | */
119 | public static void print(double[][] path)
120 | {
121 | System.out.println("X: \t Y:");
122 |
123 | for(double[] u: path)
124 | System.out.println(u[0]+ "\t" +u[1]);
125 | }
126 |
127 | /**
128 | * Performs a deep copy of a 2 Dimensional Array looping thorough each element in the 2D array
129 | *
130 | * BigO: Order N x M
131 | * @param arr
132 | * @return
133 | */
134 | public static double[][] doubleArrayCopy(double[][] arr)
135 | {
136 |
137 | //size first dimension of array
138 | double[][] temp = new double[arr.length][arr[0].length];
139 |
140 | for(int i=0; i= tolerance)
222 | {
223 | change = 0.0;
224 | for(int i=1; i li = new LinkedList();
249 |
250 | //save first value
251 | li.add(path[0]);
252 |
253 | //find intermediate nodes
254 | for(int i=1; i=0.01)
262 | li.add(path[i]);
263 | }
264 |
265 | //save last
266 | li.add(path[path.length-1]);
267 |
268 | //re-write nodes into new 2D Array
269 | double[][] temp = new double[li.size()][2];
270 |
271 | for (int i = 0; i < li.size(); i++)
272 | {
273 | temp[i][0] = li.get(i)[0];
274 | temp[i][1] = li.get(i)[1];
275 | }
276 |
277 | return temp;
278 | }
279 |
280 |
281 | /**
282 | * Returns Velocity as a double array. The First Column vector is time, based on the time step, the second vector
283 | * is the velocity magnitude.
284 | *
285 | * BigO: order N
286 | * @param smoothPath
287 | * @param timeStep
288 | * @return
289 | */
290 | double[][] velocity(double[][] smoothPath, double timeStep)
291 | {
292 | double[] dxdt = new double[smoothPath.length];
293 | double[] dydt = new double[smoothPath.length];
294 | double[][] velocity = new double[smoothPath.length][2];
295 |
296 | //set first instance to zero
297 | dxdt[0]=0;
298 | dydt[0]=0;
299 | velocity[0][0]=0;
300 | velocity[0][1]=0;
301 | heading[0][1]=0;
302 |
303 | for(int i=1; i tolerance)
362 | {
363 | increase = difference[difference.length-1]/1/50;
364 |
365 | for(int i=1;ioldPointsTotal)
453 | {
454 | first=i;
455 | second=j;
456 | numFinalPoints=pointsTotal;
457 | oldPointsTotal=pointsTotal;
458 | }
459 | }
460 |
461 | ret = new int[] {first, second, third};
462 | }
463 | else
464 | {
465 |
466 | double pointsFirst = 0;
467 | double pointsSecond = 0;
468 | double pointsTotal = 0;
469 |
470 | for (int i=1; i<=5; i++)
471 | for (int j=1; j<=8; j++)
472 | for (int k=1; k<8; k++)
473 | {
474 | pointsFirst = i *(numNodeOnlyPoints-1) + numNodeOnlyPoints;
475 | pointsSecond = (j*(pointsFirst-1)+pointsFirst);
476 | pointsTotal = (k*(pointsSecond-1)+pointsSecond);
477 |
478 | if(pointsTotal<=totalPoints)
479 | {
480 | first=i;
481 | second=j;
482 | third=k;
483 | numFinalPoints=pointsTotal;
484 | }
485 | }
486 |
487 | ret = new int[] {first, second, third};
488 | }
489 |
490 |
491 | return ret;
492 | }
493 | /**
494 | * Calculates the left and right wheel paths based on robot track width
495 | *
496 | * Big O: 2N
497 | *
498 | * @param smoothPath - center smooth path of robot
499 | * @param robotTrackWidth - width between left and right wheels of robot of skid steer chassis.
500 | */
501 | public void leftRight(double[][] smoothPath, double robotTrackWidth)
502 | {
503 |
504 | double[][] leftPath = new double[smoothPath.length][2];
505 | double[][] rightPath = new double[smoothPath.length][2];
506 |
507 | double[][] gradient = new double[smoothPath.length][2];
508 |
509 | for(int i = 0; i0)
529 | {
530 | if((deg-gradient[i-1][1])>180)
531 | gradient[i][1] = -360+deg;
532 |
533 | if((deg-gradient[i-1][1])<-180)
534 | gradient[i][1] = 360+deg;
535 | }
536 |
537 |
538 |
539 | }
540 |
541 | this.heading = gradient;
542 | this.leftPath = leftPath;
543 | this.rightPath = rightPath;
544 | }
545 |
546 | /**
547 | * Returns the first column of a 2D array of doubles
548 | * @param arr 2D array of doubles
549 | * @return array of doubles representing the 1st column of the initial parameter
550 | */
551 |
552 | public static double[] getXVector(double[][] arr)
553 | {
554 | double[] temp = new double[arr.length];
555 |
556 | for(int i=0; i