ilqgames
A new real-time solver for large-scale differential games.
polyline2.cpp
1 /*
2  * Copyright (c) 2019, The Regents of the University of California (Regents).
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above
13  * copyright notice, this list of conditions and the following
14  * disclaimer in the documentation and/or other materials provided
15  * with the distribution.
16  *
17  * 3. Neither the name of the copyright holder nor the names of its
18  * contributors may be used to endorse or promote products derived
19  * from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS
22  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
25  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31  * POSSIBILITY OF SUCH DAMAGE.
32  *
33  * Please contact the author(s) of this library if you have any questions.
34  * Authors: David Fridovich-Keil ( dfk@eecs.berkeley.edu )
35  */
36 
37 ///////////////////////////////////////////////////////////////////////////////
38 //
39 // Polyline2 class for piecewise linear paths in 2D.
40 //
41 ///////////////////////////////////////////////////////////////////////////////
42 
43 #include <ilqgames/geometry/line_segment2.h>
44 #include <ilqgames/geometry/polyline2.h>
45 #include <ilqgames/utils/types.h>
46 
47 #include <glog/logging.h>
48 
49 namespace ilqgames {
50 
51 Polyline2::Polyline2(const PointList2& points) : length_(0.0) {
52  CHECK_GT(points.size(), 1);
53  cumulative_lengths_.push_back(length_);
54 
55  // Parse into list of line segents.
56  for (size_t ii = 1; ii < points.size(); ii++) {
57  segments_.emplace_back(points[ii - 1], points[ii]);
58  length_ += segments_.back().Length();
59  cumulative_lengths_.push_back(length_);
60  }
61 }
62 
63 void Polyline2::AddPoint(const Point2& point) {
64  segments_.emplace_back(segments_.back().SecondPoint(), point);
65  length_ += segments_.back().Length();
66 }
67 
68 Point2 Polyline2::PointAt(float route_pos, bool* is_vertex,
69  LineSegment2* segment, bool* is_endpoint) const {
70  auto upper = std::upper_bound(cumulative_lengths_.begin(),
71  cumulative_lengths_.end(), route_pos);
72  if (upper == cumulative_lengths_.end()) {
73  LOG(WARNING) << "Route position " << route_pos
74  << " was off the end of the route.";
75  upper--;
76  }
77 
78  // Find the index of the line segment which contains this route position.
79  upper--;
80  const size_t idx = std::distance(cumulative_lengths_.begin(), upper);
81  if (segment) *segment = segments_[idx];
82 
83  // Walk along this line segment the remaining distance.
84  const float remaining = route_pos - cumulative_lengths_[idx];
85  CHECK_GE(remaining, 0.0);
86 
87  if (is_vertex) {
88  *is_vertex = remaining < constants::kSmallNumber ||
89  remaining > segments_[idx].Length();
90  }
91 
92  const Point2 return_point =
93  segments_[idx].FirstPoint() + remaining * segments_[idx].UnitDirection();
94  if (is_endpoint) {
95  if (idx != 0 && idx != segments_.size() - 1)
96  *is_endpoint = false;
97  else
98  *is_endpoint = (return_point == segments_.front().FirstPoint()) ||
99  (return_point == segments_.back().SecondPoint());
100  }
101 
102  return return_point;
103 }
104 
105 Point2 Polyline2::ClosestPoint(const Point2& query, bool* is_vertex,
106  LineSegment2* segment,
107  float* signed_squared_distance,
108  bool* is_endpoint) const {
109  // Walk along each line segment and remember which was closest.
110  float closest_signed_squared_distance = constants::kInfinity;
111  Point2 closest_point;
112 
113  float current_signed_squared_distance;
114  int segment_idx = 0;
115  int segment_counter = 0;
116  bool is_segment_endpoint;
117  for (const auto& s : segments_) {
118  const Point2 current_point = s.ClosestPoint(
119  query, &is_segment_endpoint, &current_signed_squared_distance);
120 
121  if (std::abs(current_signed_squared_distance) <
122  std::abs(closest_signed_squared_distance)) {
123  // If this is an endpoint, compute which side of the polyline this is on
124  // by finding which side of the line segment from the previous point to
125  // the next point this is on.
126  if (is_segment_endpoint &&
127  (segment_counter > 0 || current_point == s.SecondPoint()) &&
128  (segment_counter < segments_.size() - 1 ||
129  current_point == s.FirstPoint())) {
130  const LineSegment2 shortcut =
131  (current_point == s.FirstPoint())
132  ? LineSegment2(segments_[segment_counter - 1].FirstPoint(),
133  s.SecondPoint())
134  : LineSegment2(s.FirstPoint(),
135  segments_[segment_counter + 1].SecondPoint());
136  current_signed_squared_distance *=
137  (shortcut.Side(query)) ? sgn(current_signed_squared_distance)
138  : -sgn(current_signed_squared_distance);
139 
140  CHECK(
141  (current_signed_squared_distance >= 0.0 && shortcut.Side(query)) ||
142  (current_signed_squared_distance <= 0.0 && !shortcut.Side(query)));
143  }
144 
145  closest_signed_squared_distance = current_signed_squared_distance;
146  closest_point = current_point;
147 
148  if (is_vertex) *is_vertex = is_segment_endpoint;
149  segment_idx = segment_counter;
150  }
151 
152  segment_counter++;
153  }
154 
155  // Maybe set segment.
156  if (segment) *segment = segments_[segment_idx];
157 
158  // Maybe set signed_squared_distance.
159  if (signed_squared_distance)
160  *signed_squared_distance = closest_signed_squared_distance;
161 
162  // Check if the closest point occurs at an endpoint for the polyline.
163  if (is_endpoint) {
164  auto is_same_point = [](const Point2& p1, const Point2& p2) {
165  return (p1 - p2).squaredNorm() < constants::kSmallNumber;
166  }; // is_same_point
167 
168  *is_endpoint =
169  is_same_point(closest_point, segments_.front().FirstPoint()) ||
170  is_same_point(closest_point, segments_.back().SecondPoint());
171  }
172 
173  return closest_point;
174 } // namespace ilqgames
175 
176 } // namespace ilqgames