Add Pure Pursuit
This commit is contained in:
parent
af98e92fe8
commit
bd58f94e9a
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
#include "fl/mathematics/control/RamseteTracker.h"
|
#include "fl/mathematics/control/RamseteTracker.h"
|
||||||
#include "fl/mathematics/control/TrajectoryTracker.h"
|
#include "fl/mathematics/control/TrajectoryTracker.h"
|
||||||
|
#include "fl/mathematics/control/PurePursuitTracker.h"
|
||||||
|
|
||||||
#include "fl/mathematics/spline/ParametricQuinticHermiteSpline.h"
|
#include "fl/mathematics/spline/ParametricQuinticHermiteSpline.h"
|
||||||
#include "fl/mathematics/spline/ParametricSpline.h"
|
#include "fl/mathematics/spline/ParametricSpline.h"
|
||||||
|
|
83
src/include/fl/mathematics/control/PurePursuitTracker.h
Normal file
83
src/include/fl/mathematics/control/PurePursuitTracker.h
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "TrajectoryTracker.h"
|
||||||
|
#include "fl/mathematics/geometry/Pose2d.h"
|
||||||
|
#include "fl/mathematics/geometry/Pose2dWithCurvature.h"
|
||||||
|
|
||||||
|
namespace fl {
|
||||||
|
class PurePursuitTracker : public TrajectoryTracker {
|
||||||
|
public:
|
||||||
|
PurePursuitTracker(const double lat, const double lookahead_time, const double min_lookahead_distance)
|
||||||
|
: lat_(lat), lookahead_time_(lookahead_time), min_lookahead_distance_(min_lookahead_distance) {}
|
||||||
|
|
||||||
|
TrajectoryTrackerVelocityOutput CalculateState(const TimedIterator<Pose2dWithCurvature>& iterator,
|
||||||
|
const Pose2d& robot_pose) const override {
|
||||||
|
const auto reference_point = iterator.CurrentState();
|
||||||
|
|
||||||
|
// Compute the lookahead state.
|
||||||
|
const auto lookahead_state = CalculateLookaheadPose(iterator, robot_pose);
|
||||||
|
|
||||||
|
// Find the appropriate lookahead point.
|
||||||
|
const auto lookahead_transform = lookahead_state.InFrameOfReferenceOf(robot_pose);
|
||||||
|
|
||||||
|
// Calculate latitude error.
|
||||||
|
const auto x_error =
|
||||||
|
reference_point.state.State().Pose().InFrameOfReferenceOf(robot_pose).Translation().X();
|
||||||
|
|
||||||
|
// Calculate the velocity at the reference point.
|
||||||
|
const auto vd = reference_point.state.Velocity();
|
||||||
|
|
||||||
|
// Calculate the distance from the robot to the lookahead.
|
||||||
|
const auto l = lookahead_transform.Translation().Norm();
|
||||||
|
|
||||||
|
// Calculate the curvature of the arc that connects the robot and the lookahead point.
|
||||||
|
const auto curvature = 2 * lookahead_transform.Translation().Y() / std::pow(l, 2);
|
||||||
|
|
||||||
|
// Adjust the linear velocity to compensate for the robot lagging behind.
|
||||||
|
const auto adjusted_linear_velocity = vd * lookahead_transform.Rotation().Cos() + lat_ * x_error;
|
||||||
|
|
||||||
|
return {adjusted_linear_velocity, adjusted_linear_velocity * curvature};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
double lat_;
|
||||||
|
double lookahead_time_;
|
||||||
|
double min_lookahead_distance_;
|
||||||
|
|
||||||
|
Pose2d CalculateLookaheadPose(const TimedIterator<Pose2dWithCurvature>& iterator,
|
||||||
|
const Pose2d& robot_pose) const {
|
||||||
|
auto lookahead_pose_by_time = iterator.Preview(lookahead_time_).state.State().Pose();
|
||||||
|
|
||||||
|
// The lookahead point is farther from the robot than the minimum lookahead distance.
|
||||||
|
// Therefore we can use this point.
|
||||||
|
if (lookahead_pose_by_time.InFrameOfReferenceOf(robot_pose).Translation().Norm() >=
|
||||||
|
min_lookahead_distance_) {
|
||||||
|
return lookahead_pose_by_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lookahead_pose_by_distance = iterator.CurrentState().state.State().Pose();
|
||||||
|
auto previewed_time = 0.0;
|
||||||
|
|
||||||
|
// Run the loop until a distance that is greater than the minimum lookahead distance is found or until
|
||||||
|
// we run out of "trajectory" to search. If this happens, we will simply extend the end of the trajectory.
|
||||||
|
while (iterator.Progress() > previewed_time) {
|
||||||
|
previewed_time += 0.02;
|
||||||
|
lookahead_pose_by_distance = iterator.Preview(previewed_time).state.State().Pose();
|
||||||
|
|
||||||
|
const auto lookahead_distance =
|
||||||
|
lookahead_pose_by_distance.InFrameOfReferenceOf(robot_pose).Translation().Norm();
|
||||||
|
|
||||||
|
if (lookahead_distance > min_lookahead_distance_) {
|
||||||
|
return lookahead_pose_by_distance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto remaining = min_lookahead_distance_ -
|
||||||
|
(lookahead_pose_by_distance.InFrameOfReferenceOf(robot_pose)).Translation().Norm();
|
||||||
|
|
||||||
|
// Extend the trajectory
|
||||||
|
return lookahead_pose_by_distance.TransformBy(
|
||||||
|
Pose2d{Translation2d{remaining * (iterator.Reversed() ? -1.0 : 1.0), 0.0}, Rotation2d{}});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace fl
|
|
@ -54,6 +54,8 @@ class Translation2d final : public VaryInterpolatable<Translation2d> {
|
||||||
double X() const { return x_; }
|
double X() const { return x_; }
|
||||||
double Y() const { return y_; }
|
double Y() const { return y_; }
|
||||||
|
|
||||||
|
double Norm() const { return std::hypot(x_, y_); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
double x_;
|
double x_;
|
||||||
double y_;
|
double y_;
|
||||||
|
|
|
@ -23,7 +23,7 @@ class TrajectoryIterator {
|
||||||
return sample_;
|
return sample_;
|
||||||
}
|
}
|
||||||
|
|
||||||
TrajectorySamplePoint<S> Preview(U amount) {
|
TrajectorySamplePoint<S> Preview(U amount) const {
|
||||||
auto progress =
|
auto progress =
|
||||||
Clamp(Addition(progress_, amount), trajectory_->FirstInterpolant(), trajectory_->LastInterpolant());
|
Clamp(Addition(progress_, amount), trajectory_->FirstInterpolant(), trajectory_->LastInterpolant());
|
||||||
return trajectory_->Sample(progress);
|
return trajectory_->Sample(progress);
|
||||||
|
@ -32,6 +32,9 @@ class TrajectoryIterator {
|
||||||
bool IsDone() const { return progress_ >= trajectory_->LastInterpolant(); }
|
bool IsDone() const { return progress_ >= trajectory_->LastInterpolant(); }
|
||||||
TrajectorySamplePoint<S> CurrentState() const { return sample_; }
|
TrajectorySamplePoint<S> CurrentState() const { return sample_; }
|
||||||
|
|
||||||
|
U Progress() const { return progress_; }
|
||||||
|
bool Reversed() const { return trajectory_->Reversed(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual U Addition(U a, U b) const = 0;
|
virtual U Addition(U a, U b) const = 0;
|
||||||
|
|
||||||
|
|
39
src/test/cpp/pure-pursuit-tests.cpp
Normal file
39
src/test/cpp/pure-pursuit-tests.cpp
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include "FalconLibrary.h"
|
||||||
|
|
||||||
|
constexpr double kTestEpsilon = 1E-9;
|
||||||
|
|
||||||
|
class PurePursuitTest : public ::testing::Test {
|
||||||
|
public:
|
||||||
|
void Run(fl::Pose2d initial, fl::Pose2d final, double max_velocity = 3, double max_acceleration = 2,
|
||||||
|
bool backwards = false) {
|
||||||
|
auto trajectory = fl::TrajectoryGenerator::GenerateTrajectory(
|
||||||
|
std::vector<fl::Pose2d>{initial, final},
|
||||||
|
std::vector<fl::TimingConstraint<fl::Pose2dWithCurvature>*>{}, 0.0, 0.0, max_velocity,
|
||||||
|
max_acceleration, backwards);
|
||||||
|
|
||||||
|
fl::PurePursuitTracker tracker{3.0, 2.0, 0.3};
|
||||||
|
tracker.Reset(trajectory);
|
||||||
|
|
||||||
|
fl::Pose2d robot_pose = initial;
|
||||||
|
double t = 0.0;
|
||||||
|
|
||||||
|
while (!tracker.IsFinished()) {
|
||||||
|
const auto output = tracker.NextState(robot_pose, t);
|
||||||
|
|
||||||
|
fl::Twist2d twist{output.linear_velocity * 0.02, 0.0, output.angular_velocity * 0.02};
|
||||||
|
robot_pose = robot_pose + fl::Pose2d::FromTwist(twist);
|
||||||
|
|
||||||
|
t += 0.02;
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_NEAR(robot_pose.Translation().X(), final.Translation().X(), 0.1);
|
||||||
|
EXPECT_NEAR(robot_pose.Translation().Y(), final.Translation().Y(), 0.1);
|
||||||
|
EXPECT_NEAR(robot_pose.Rotation().Radians(), final.Rotation().Radians(), 0.15);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(PurePursuitTest, Test) {
|
||||||
|
Run(fl::Pose2d{0.0, 0.0, fl::Rotation2d::FromDegrees(0.0)},
|
||||||
|
fl::Pose2d{10.0, 10.0, fl::Rotation2d::FromDegrees(50.0)});
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user