forked from RobotLocomotion/drake
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement DoToLanePosition for (Arc|Line)Lanes (RobotLocomotion#5780)
* Implement DoToLanePosition for (Arc|Line)Lanes
- Loading branch information
1 parent
01b58cb
commit d997355
Showing
7 changed files
with
398 additions
and
82 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,24 +5,78 @@ | |
#include <limits> | ||
|
||
#include "drake/common/drake_assert.h" | ||
#include "drake/math/saturate.h" | ||
|
||
namespace drake { | ||
namespace maliput { | ||
namespace monolane { | ||
|
||
namespace { | ||
|
||
// Wraps the input angle θ, casting it onto the range [-π, π]. | ||
double wrap(double theta) { | ||
double theta_new = std::fmod(theta + M_PI, 2. * M_PI); | ||
if (theta_new < 0.) theta_new += 2. * M_PI; | ||
return theta_new - M_PI; | ||
} | ||
|
||
// Implements the saturate function with respect to a range of angles. | ||
// Specifically, given an angle θ ∈ [-π, π] along with lower and upper bounds | ||
// θ_min, θ_max, such that -∞ < θ_min <= θ_max < +∞, this function returns the | ||
// saturated angle sat(θ, θ_min, θ_max) within the range [-π, π]. If θ_max > 2π | ||
// + θ_min, then no saturation occurs. | ||
// | ||
// Note that the interval [θ_min, θ_max] should be mapped into the cyclic | ||
// range [-π, +π] --- and in doing so, it might remain a single closed | ||
// interval [a, b], or it might split into a pair of intervals [-π, a] and [b, | ||
// +π]. In both cases, -π <= a <= b <= +π. If the result is a single | ||
// interval, saturating is the usual. If the result is two intervals, saturating | ||
// involves the extra step picking the 'closest' interval to saturate | ||
// within. | ||
double saturate_on_wrapped_bounds(double theta, double theta_min, | ||
double theta_max) { | ||
DRAKE_DEMAND(-M_PI <= theta); | ||
DRAKE_DEMAND(theta <= M_PI); | ||
DRAKE_DEMAND(theta_min <= theta_max); | ||
|
||
if (theta_max >= theta_min + 2. * M_PI) return theta; | ||
|
||
// Wrap on the range [-π, π]. This is not order-preserving. | ||
const double theta_0 = wrap(theta_min); | ||
const double theta_1 = wrap(theta_max); | ||
|
||
// Criteria for returning the unsaturated result. | ||
// First, deal with the case where [θ_min, θ_max] wrapped does not cross the | ||
// ±π wrap-point (i.e. θ₀ ≤ θ₁). | ||
if (theta_0 <= theta && theta <= theta_1) return theta; | ||
// Next, deal with the case where [θ_min, θ_max] wrapped straddles ±π (i.e. θ₁ | ||
// < θ₀). | ||
if (theta <= theta_1 && theta_1 < theta_0) return theta; | ||
if (theta_1 < theta_0 && theta_0 <= theta) return theta; | ||
|
||
// Saturate at the appropriate bound. | ||
const double delta_0 = std::min(std::abs(theta - theta_0), | ||
std::abs(theta - 2. * M_PI - theta_0)); | ||
const double delta_1 = std::min(std::abs(theta - theta_1), | ||
std::abs(theta + 2. * M_PI - theta_1)); | ||
return (delta_0 <= delta_1) ? theta_0 : theta_1; | ||
} | ||
|
||
} // namespace | ||
|
||
ArcLane::ArcLane(const api::LaneId& id, const api::Segment* segment, | ||
const V2& center, double radius, | ||
const double theta0, const double d_theta, | ||
const api::RBounds& lane_bounds, | ||
const V2& center, double radius, const double theta0, | ||
const double d_theta, const api::RBounds& lane_bounds, | ||
const api::RBounds& driveable_bounds, | ||
const CubicPolynomial& elevation, | ||
const CubicPolynomial& superelevation) | ||
: Lane(id, segment, | ||
lane_bounds, driveable_bounds, | ||
radius * std::abs(d_theta), | ||
elevation, superelevation), | ||
r_(radius), cx_(center.x()), cy_(center.y()), | ||
theta0_(theta0), d_theta_(d_theta) { | ||
: Lane(id, segment, lane_bounds, driveable_bounds, | ||
radius * std::abs(d_theta), elevation, superelevation), | ||
r_(radius), | ||
cx_(center.x()), | ||
cy_(center.y()), | ||
theta0_(theta0), | ||
d_theta_(d_theta) { | ||
DRAKE_DEMAND(r_ > 0.); | ||
|
||
// Whether or not user code pays attention to driveable_bounds, at least | ||
|
@@ -41,10 +95,12 @@ ArcLane::ArcLane(const api::LaneId& id, const api::Segment* segment, | |
// at which an extremum can occur, since superelevation is cubic. | ||
double theta_min = std::numeric_limits<double>::max(); | ||
double theta_max = std::numeric_limits<double>::min(); | ||
auto update_range_given_p = | ||
[&theta_min, &theta_max, &superelevation](double p) { | ||
auto update_range_given_p = [&theta_min, &theta_max, | ||
&superelevation](double p) { | ||
// Skip p if outside of domain [0, 1]. | ||
if ((p < 0.) || (p > 1.)) { return; } | ||
if ((p < 0.) || (p > 1.)) { | ||
return; | ||
} | ||
const double theta_p = superelevation.f_p(p); | ||
theta_min = std::min(theta_min, theta_p); | ||
theta_max = std::max(theta_max, theta_p); | ||
|
@@ -58,25 +114,25 @@ ArcLane::ArcLane(const api::LaneId& id, const api::Segment* segment, | |
if (superelevation.d() != 0) { | ||
const double p_plus = | ||
(-superelevation.c() + | ||
std::sqrt((superelevation.c() * superelevation.c()) | ||
- (3. * superelevation.b() * superelevation.d()))) | ||
/ (3. * superelevation.d()); | ||
std::sqrt((superelevation.c() * superelevation.c()) - | ||
(3. * superelevation.b() * superelevation.d()))) / | ||
(3. * superelevation.d()); | ||
update_range_given_p(p_plus); | ||
const double p_minus = | ||
(-superelevation.c() - | ||
std::sqrt((superelevation.c() * superelevation.c()) | ||
- (3. * superelevation.b() * superelevation.d()))) | ||
/ (3. * superelevation.d()); | ||
std::sqrt((superelevation.c() * superelevation.c()) - | ||
(3. * superelevation.b() * superelevation.d()))) / | ||
(3. * superelevation.d()); | ||
update_range_given_p(p_minus); | ||
} else if (superelevation.c() != 0) { | ||
const double p_extremum = | ||
-superelevation.b() / (2. * superelevation.c()); | ||
const double p_extremum = -superelevation.b() / (2. * superelevation.c()); | ||
update_range_given_p(p_extremum); | ||
} | ||
// If theta_min and theta_max flank zero, then min(abs(theta)) is 0.... | ||
const double max_cos_theta = | ||
std::cos(((theta_min < 0.) && (theta_max > 0.)) ? 0. | ||
: std::min(std::abs(theta_min), std::abs(theta_max))); | ||
std::cos(((theta_min < 0.) && (theta_max > 0.)) | ||
? 0. | ||
: std::min(std::abs(theta_min), std::abs(theta_max))); | ||
// TODO([email protected]) When you have nothing better to do, handle the | ||
// improbable case of superelevation >= 90 deg, too. | ||
if (d_theta > 0.) { | ||
|
@@ -86,22 +142,18 @@ ArcLane::ArcLane(const api::LaneId& id, const api::Segment* segment, | |
} | ||
} | ||
|
||
|
||
// Evaluate absolute position along reference arc as an angle in | ||
// range [theta0_, (theta0 + d_theta_)], | ||
// as a function of parameter p (in domain [0, 1]). | ||
double ArcLane::theta_of_p(const double p) const { | ||
return theta0_ + (p * d_theta_); | ||
} | ||
|
||
|
||
V2 ArcLane::xy_of_p(const double p) const { | ||
const double theta = theta_of_p(p); | ||
return V2(cx_ + (r_ * std::cos(theta)), | ||
cy_ + (r_ * std::sin(theta))); | ||
return V2(cx_ + (r_ * std::cos(theta)), cy_ + (r_ * std::sin(theta))); | ||
} | ||
|
||
|
||
V2 ArcLane::xy_dot_of_p(const double p) const { | ||
// Given: | ||
// x = c_x + (r * cos(θ)) | ||
|
@@ -113,17 +165,14 @@ V2 ArcLane::xy_dot_of_p(const double p) const { | |
// | ||
// A similar result holds for y. | ||
const double theta = theta_of_p(p); | ||
return V2(-r_ * std::sin(theta) * d_theta_, | ||
r_ * std::cos(theta) * d_theta_); | ||
return V2(-r_ * std::sin(theta) * d_theta_, r_ * std::cos(theta) * d_theta_); | ||
} | ||
|
||
|
||
double ArcLane::heading_of_p(const double p) const { | ||
const double theta = theta_of_p(p); | ||
return theta + std::copysign(M_PI / 2.0, d_theta_); | ||
} | ||
|
||
|
||
double ArcLane::heading_dot_of_p(const double p) const { | ||
// Given: | ||
// H = θ ± (π / 2) | ||
|
@@ -134,11 +183,59 @@ double ArcLane::heading_dot_of_p(const double p) const { | |
return d_theta_; | ||
} | ||
|
||
api::LanePosition ArcLane::DoToLanePosition( | ||
const api::GeoPosition& geo_position, api::GeoPosition* nearest_position, | ||
double* distance) const { | ||
// TODO(jadecastro): Lift the zero superelevation and zero elevation gradient | ||
// restriction. | ||
const V2 center{cx_, cy_}; | ||
const V2 p{geo_position.x, geo_position.y}; | ||
DRAKE_DEMAND(p != center); | ||
|
||
// Define a vector from p to the center of the arc. | ||
const V2 v = p - center; | ||
|
||
const double theta_min = std::min(theta0_, d_theta_ + theta0_); | ||
const double theta_max = std::max(theta0_, d_theta_ + theta0_); | ||
|
||
// First, find a saturated theta that is nearest to point p. | ||
const double theta_nearest = | ||
saturate_on_wrapped_bounds(std::atan2(v(1), v(0)), theta_min, theta_max); | ||
// Find the angle swept from the beginning of the lane (s = 0) to | ||
// theta_nearest. | ||
const double d_theta_nearest = (d_theta_ > 0.) | ||
? theta_nearest - wrap(theta_min) : wrap(theta_max) - theta_nearest; | ||
// Then, unwrap this angle (if necessary) to deal with possible crossings with | ||
// the ±π wrap-around point. | ||
const double d_theta_nearest_unwrapped = | ||
(d_theta_nearest < 0.) ? d_theta_nearest + 2. * M_PI : d_theta_nearest; | ||
// Convert this angular displacement to arc length (s). | ||
const double s = r_ * d_theta_nearest_unwrapped; | ||
|
||
// Compute r (its direction depends on the direction of the +s-coordinate) | ||
const double r_unsaturated = (d_theta_ >= 0.) ? r_ - v.norm() : v.norm() - r_; | ||
// Saturate r within drivable bounds. | ||
const double r = math::saturate(r_unsaturated, driveable_bounds(s).r_min, | ||
driveable_bounds(s).r_max); | ||
|
||
// Calculate the (uniform) road elevation. | ||
const double p_scale = r_ * d_theta_; | ||
// N.B. h is the geo z-coordinate referenced against the lane elevation (whose | ||
// `a` coefficient is normalized by lane length). | ||
const double h = geo_position.z - elevation().a() * p_scale; | ||
|
||
const api::LanePosition lane_position{s, r, h}; | ||
|
||
const api::GeoPosition nearest = ToGeoPosition(lane_position); | ||
if (nearest_position != nullptr) { | ||
*nearest_position = nearest; | ||
} | ||
if (distance != nullptr) { | ||
const V2 p_to_nearest{p(0) - nearest.x, p(1) - nearest.y}; | ||
*distance = p_to_nearest.norm(); | ||
} | ||
|
||
api::LanePosition ArcLane::DoToLanePosition(const api::GeoPosition&, | ||
api::GeoPosition*, | ||
double*) const { | ||
DRAKE_ABORT(); // TODO([email protected]) Implement me. | ||
return lane_position; | ||
} | ||
|
||
} // namespace monolane | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,36 +3,60 @@ | |
#include <cmath> | ||
|
||
#include "drake/common/drake_assert.h" | ||
#include "drake/math/saturate.h" | ||
|
||
namespace drake { | ||
namespace maliput { | ||
namespace monolane { | ||
|
||
V2 LineLane::xy_of_p(const double p) const { | ||
return V2(x0_ + (p * dx_), | ||
y0_ + (p * dy_)); | ||
} | ||
|
||
|
||
V2 LineLane::xy_dot_of_p(const double p) const { | ||
return V2(dx_, | ||
dy_); | ||
return V2(x0_ + (p * dx_), y0_ + (p * dy_)); | ||
} | ||
|
||
V2 LineLane::xy_dot_of_p(const double p) const { return V2(dx_, dy_); } | ||
|
||
double LineLane::heading_of_p(const double) const { return heading_; } | ||
|
||
|
||
double LineLane::heading_dot_of_p(const double) const { return 0.; } | ||
|
||
|
||
api::LanePosition LineLane::DoToLanePosition(const api::GeoPosition&, | ||
api::GeoPosition*, | ||
double*) const { | ||
DRAKE_ABORT(); // TODO([email protected]) Implement me. | ||
api::LanePosition LineLane::DoToLanePosition( | ||
const api::GeoPosition& geo_position, api::GeoPosition* nearest_position, | ||
double* distance) const { | ||
// TODO(jadecastro): Lift the zero superelevation and zero elevation gradient | ||
// restriction. | ||
const V2 xy0{x0_, y0_}; | ||
const V2 d_xy{dx_, dy_}; | ||
const double length = this->length(); | ||
const V2 s_unit_vector = d_xy / d_xy.norm(); | ||
const V2 r_unit_vector{-s_unit_vector(1), s_unit_vector(0)}; | ||
|
||
const V2 p{geo_position.x, geo_position.y}; | ||
const V2 lane_origin_to_p = p - xy0; | ||
|
||
// Compute the distance from `p` to the start of the lane. | ||
const double s_unsaturated = lane_origin_to_p.dot(s_unit_vector); | ||
const double s = math::saturate(s_unsaturated, 0., length); | ||
const double r_unsaturated = lane_origin_to_p.dot(r_unit_vector); | ||
const double r = math::saturate(r_unsaturated, driveable_bounds(s).r_min, | ||
driveable_bounds(s).r_max); | ||
// N.B. h is the geo z-coordinate referenced against the lane elevation (whose | ||
// `a` coefficient is normalized by lane length). | ||
const double h = geo_position.z - elevation().a() * length; | ||
|
||
const api::LanePosition lane_position{s, r, h}; | ||
|
||
const api::GeoPosition nearest = ToGeoPosition(lane_position); | ||
if (nearest_position != nullptr) { | ||
*nearest_position = nearest; | ||
} | ||
if (distance != nullptr) { | ||
const V2 p_to_nearest{p(0) - nearest.x, p(1) - nearest.y}; | ||
*distance = p_to_nearest.norm(); | ||
} | ||
|
||
return lane_position; | ||
} | ||
|
||
|
||
} // namespace monolane | ||
} // namespace maliput | ||
} // namespace drake |
Oops, something went wrong.