/src/s2geometry/src/s2/s2polyline_simplifier.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright 2016 Google Inc. All Rights Reserved. |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS-IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | // |
15 | | |
16 | | // Author: ericv@google.com (Eric Veach) |
17 | | |
18 | | #include "s2/s2polyline_simplifier.h" |
19 | | |
20 | | #include <cfloat> |
21 | | #include <cmath> |
22 | | #include <vector> |
23 | | |
24 | | #include "absl/log/absl_check.h" |
25 | | #include "s2/s1chord_angle.h" |
26 | | #include "s2/s1interval.h" |
27 | | #include "s2/s2point.h" |
28 | | |
29 | 0 | void S2PolylineSimplifier::Init(const S2Point& src) { |
30 | 0 | src_ = src; |
31 | 0 | window_ = S1Interval::Full(); |
32 | 0 | ranges_to_avoid_.clear(); |
33 | | |
34 | | // Precompute basis vectors for the tangent space at "src". This is similar |
35 | | // to GetFrame() except that we don't normalize the vectors. As it turns |
36 | | // out, the two basis vectors below have the same magnitude (up to the |
37 | | // length error in S2Point::Normalize). |
38 | | |
39 | | // Find the index of the component whose magnitude is smallest. |
40 | 0 | S2Point tmp = src.Abs(); |
41 | 0 | int i = (tmp[0] < tmp[1] ? |
42 | 0 | (tmp[0] < tmp[2] ? 0 : 2) : (tmp[1] < tmp[2] ? 1 : 2)); |
43 | | |
44 | | // We define the "y" basis vector as the cross product of "src" and the |
45 | | // basis vector for axis "i". Let "j" and "k" be the indices of the other |
46 | | // two components in cyclic order. |
47 | 0 | int j = (i == 2 ? 0 : i + 1), k = (i == 0 ? 2 : i - 1); |
48 | 0 | y_dir_[i] = 0; |
49 | 0 | y_dir_[j] = src[k]; |
50 | 0 | y_dir_[k] = -src[j]; |
51 | | |
52 | | // Compute the cross product of "y_dir" and "src". We write out the cross |
53 | | // product here mainly for documentation purposes; it also happens to save a |
54 | | // few multiplies because unfortunately the optimizer does *not* get rid of |
55 | | // multiplies by zero (since these multiplies propagate NaN, for example). |
56 | 0 | x_dir_[i] = src[j] * src[j] + src[k] * src[k]; |
57 | 0 | x_dir_[j] = -src[j] * src[i]; |
58 | 0 | x_dir_[k] = -src[k] * src[i]; |
59 | 0 | } |
60 | | |
61 | 0 | bool S2PolylineSimplifier::Extend(const S2Point& dst) const { |
62 | | // We limit the maximum edge length to 90 degrees in order to simplify the |
63 | | // error bounds. (The error gets arbitrarily large as the edge length |
64 | | // approaches 180 degrees.) |
65 | 0 | if (S1ChordAngle(src_, dst) > S1ChordAngle::Right()) return false; |
66 | | |
67 | | // Otherwise check whether this vertex is in the acceptable angle range. |
68 | 0 | double dir = GetDirection(dst); |
69 | 0 | if (!window_.Contains(dir)) return false; |
70 | | |
71 | | // Also check any angles ranges to avoid that have not been processed yet. |
72 | 0 | for (const auto& range : ranges_to_avoid_) { |
73 | 0 | if (range.interval.Contains(dir)) return false; |
74 | 0 | } |
75 | 0 | return true; |
76 | 0 | } |
77 | | |
78 | 0 | bool S2PolylineSimplifier::TargetDisc(const S2Point& p, S1ChordAngle r) { |
79 | | // Shrink the target interval by the maximum error from all sources. This |
80 | | // guarantees that the output edge will intersect the given disc. |
81 | 0 | double semiwidth = GetSemiwidth(p, r, -1 /*round down*/); |
82 | 0 | if (semiwidth >= M_PI) { |
83 | | // The target disc contains "src", so there is nothing to do. |
84 | 0 | return true; |
85 | 0 | } |
86 | 0 | if (semiwidth < 0) { |
87 | 0 | window_ = S1Interval::Empty(); |
88 | 0 | return false; |
89 | 0 | } |
90 | | // Otherwise compute the angle interval corresponding to the target disc and |
91 | | // intersect it with the current window. |
92 | 0 | double center = GetDirection(p); |
93 | 0 | S1Interval target = S1Interval::FromPoint(center).Expanded(semiwidth); |
94 | 0 | window_ = window_.Intersection(target); |
95 | | |
96 | | // If there are any angle ranges to avoid, they can be processed now. |
97 | 0 | for (const auto& range : ranges_to_avoid_) { |
98 | 0 | AvoidRange(range.interval, range.on_left); |
99 | 0 | } |
100 | 0 | ranges_to_avoid_.clear(); |
101 | |
|
102 | 0 | return !window_.is_empty(); |
103 | 0 | } |
104 | | |
105 | | bool S2PolylineSimplifier::AvoidDisc(const S2Point& p, S1ChordAngle r, |
106 | 0 | bool disc_on_left) { |
107 | | // Expand the interval by the maximum error from all sources. This |
108 | | // guarantees that the final output edge will avoid the given disc. |
109 | 0 | double semiwidth = GetSemiwidth(p, r, 1 /*round up*/); |
110 | 0 | if (semiwidth >= M_PI) { |
111 | | // The disc to avoid contains "src", so it can't be avoided. |
112 | 0 | window_ = S1Interval::Empty(); |
113 | 0 | return false; |
114 | 0 | } |
115 | | // Compute the disallowed range of angles: the angle subtended by the disc |
116 | | // on one side, and 90 degrees on the other (to satisfy "disc_on_left"). |
117 | 0 | double center = GetDirection(p); |
118 | 0 | double dleft = disc_on_left ? M_PI_2 : semiwidth; |
119 | 0 | double dright = disc_on_left ? semiwidth : M_PI_2; |
120 | 0 | S1Interval avoid_interval(remainder(center - dright, 2 * M_PI), |
121 | 0 | remainder(center + dleft, 2 * M_PI)); |
122 | |
|
123 | 0 | if (window_.is_full()) { |
124 | | // Discs to avoid can't be processed until window_ is reduced to at most |
125 | | // 180 degrees by a call to TargetDisc(). Save it for later. |
126 | 0 | ranges_to_avoid_.push_back(RangeToAvoid{avoid_interval, disc_on_left}); |
127 | 0 | return true; |
128 | 0 | } |
129 | 0 | AvoidRange(avoid_interval, disc_on_left); |
130 | 0 | return !window_.is_empty(); |
131 | 0 | } |
132 | | |
133 | | void S2PolylineSimplifier::AvoidRange(const S1Interval& avoid_interval, |
134 | 0 | bool disc_on_left) { |
135 | | // If "avoid_interval" is a proper subset of "window_", then in theory the |
136 | | // result should be two intervals. One interval points towards the given |
137 | | // disc and passes on the correct side of it, while the other interval points |
138 | | // away from the disc. However the latter interval never contains an |
139 | | // acceptable output edge direction (as long as this class is being used |
140 | | // correctly) and can be safely ignored. This is true because (1) "window_" |
141 | | // is not full, which means that it contains at least one vertex of the input |
142 | | // polyline and is at most 180 degrees in length, and (2) "disc_on_left" is |
143 | | // computed with respect to the next edge of the input polyline, which means |
144 | | // that the next input vertex is either inside "avoid_interval" or somewhere |
145 | | // in the 180 degrees to its right/left according to "disc_on_left", which |
146 | | // means that it cannot be contained by the subinterval that we ignore. |
147 | 0 | ABSL_DCHECK(!window_.is_full()); |
148 | 0 | if (window_.Contains(avoid_interval)) { |
149 | 0 | if (disc_on_left) { |
150 | 0 | window_ = S1Interval(window_.lo(), avoid_interval.lo()); |
151 | 0 | } else { |
152 | 0 | window_ = S1Interval(avoid_interval.hi(), window_.hi()); |
153 | 0 | } |
154 | 0 | } else { |
155 | 0 | window_ = window_.Intersection(avoid_interval.Complement()); |
156 | 0 | } |
157 | 0 | } |
158 | | |
159 | 0 | double S2PolylineSimplifier::GetDirection(const S2Point& p) const { |
160 | 0 | return atan2(p.DotProd(y_dir_), p.DotProd(x_dir_)); |
161 | 0 | } |
162 | | |
163 | | // Computes half the angle in radians subtended from the source vertex by a |
164 | | // disc of radius "r" centered at "p", rounding the result conservatively up |
165 | | // or down according to whether round_direction is +1 or -1. (So for example, |
166 | | // if round_direction == +1 then the return value is an upper bound on the |
167 | | // true result.) |
168 | | double S2PolylineSimplifier::GetSemiwidth(const S2Point& p, S1ChordAngle r, |
169 | 0 | int round_direction) const { |
170 | 0 | double constexpr DBL_ERR = 0.5 * DBL_EPSILON; |
171 | | |
172 | | // Using spherical trigonometry, |
173 | | // |
174 | | // sin(semiwidth) = sin(r) / sin(a) |
175 | | // |
176 | | // where "a" is the angle between "src" and "p". Rather than measuring |
177 | | // these angles, instead we measure the squared chord lengths through the |
178 | | // interior of the sphere (i.e., Cartersian distance). Letting "r2" be the |
179 | | // squared chord distance corresponding to "r", and "a2" be the squared |
180 | | // chord distance corresponding to "a", we use the relationships |
181 | | // |
182 | | // sin^2(r) = r2 (1 - r2 / 4) |
183 | | // sin^2(a) = d2 (1 - d2 / 4) |
184 | | // |
185 | | // which follow from the fact that r2 = (2 * sin(r / 2)) ^ 2, etc. |
186 | | |
187 | | // "a2" has a relative error up to 5 * DBL_ERR, plus an absolute error of up |
188 | | // to 64 * DBL_ERR^2 (because "src" and "p" may differ from unit length by |
189 | | // up to 4 * DBL_ERR). We can correct for the relative error later, but for |
190 | | // the absolute error we use "round_direction" to account for it now. |
191 | 0 | double r2 = r.length2(); |
192 | 0 | double a2 = S1ChordAngle(src_, p).length2(); |
193 | 0 | a2 -= 64 * DBL_ERR * DBL_ERR * round_direction; |
194 | 0 | if (a2 <= r2) return M_PI; // The given disc contains "src". |
195 | | |
196 | 0 | double sin2_r = r2 * (1 - 0.25 * r2); |
197 | 0 | double sin2_a = a2 * (1 - 0.25 * a2); |
198 | 0 | double semiwidth = asin(sqrt(sin2_r / sin2_a)); |
199 | | |
200 | | // We compute bounds on the errors from all sources: |
201 | | // |
202 | | // - The call to GetSemiwidth (this call). |
203 | | // - The call to GetDirection that computes the center of the interval. |
204 | | // - The call to GetDirection in Extend that tests whether a given point |
205 | | // is an acceptable destination vertex. |
206 | | // |
207 | | // Summary of the errors in GetDirection: |
208 | | // |
209 | | // y_dir_ has no error. |
210 | | // |
211 | | // x_dir_ has a relative error of DBL_ERR in two components, a relative |
212 | | // error of 2 * DBL_ERR in the other component, plus an overall relative |
213 | | // length error of 4 * DBL_ERR (compared to y_dir_) because "src" is assumed |
214 | | // to be normalized only to within the tolerances of S2Point::Normalize(). |
215 | | // |
216 | | // p.DotProd(y_dir_) has a relative error of 1.5 * DBL_ERR and an |
217 | | // absolute error of 1.5 * DBL_ERR * y_dir_.Norm(). |
218 | | // |
219 | | // p.DotProd(x_dir_) has a relative error of 5.5 * DBL_ERR and an absolute |
220 | | // error of 3.5 * DBL_ERR * y_dir_.Norm() (noting that x_dir_ and y_dir_ |
221 | | // have the same length to within a relative error of 4 * DBL_ERR). |
222 | | // |
223 | | // It's possible to show by taking derivatives that these errors can affect |
224 | | // the angle atan2(y, x) by up 7.093 * DBL_ERR radians. Rounding up and |
225 | | // including the call to atan2 gives a final error bound of 10 * DBL_ERR. |
226 | | // |
227 | | // Summary of the errors in GetSemiwidth: |
228 | | // |
229 | | // The distance a2 has a relative error of 5 * DBL_ERR plus an absolute |
230 | | // error of 64 * DBL_ERR^2 because the points "src" and "p" may differ from |
231 | | // unit length (by up to 4 * DBL_ERR). We have already accounted for the |
232 | | // absolute error above, leaving only the relative error. |
233 | | // |
234 | | // sin2_r has a relative error of 2 * DBL_ERR. |
235 | | // |
236 | | // sin2_a has a relative error of 12 * DBL_ERR assuming that a2 <= 2, |
237 | | // i.e. distance(src, p) <= 90 degrees. (The relative error gets |
238 | | // arbitrarily larger as this distance approaches 180 degrees.) |
239 | | // |
240 | | // semiwidth has a relative error of 17 * DBL_ERR. |
241 | | // |
242 | | // Finally, (center +/- semiwidth) has a rounding error of up to 4 * DBL_ERR |
243 | | // because in theory, the result magnitude may be as large as 1.5 * M_PI |
244 | | // which is larger than 4.0. This gives a total error of: |
245 | 0 | double error = (2 * 10 + 4) * DBL_ERR + 17 * DBL_ERR * semiwidth; |
246 | 0 | return semiwidth + round_direction * error; |
247 | 0 | } |