TimeOfImpact.cpp
Go to the documentation of this file.
1 /*
2  * Original work Copyright (c) 2007-2009 Erin Catto http://www.box2d.org
3  * Modified work Copyright (c) 2017 Louis Langholtz https://github.com/louis-langholtz/PlayRho
4  *
5  * This software is provided 'as-is', without any express or implied
6  * warranty. In no event will the authors be held liable for any damages
7  * arising from the use of this software.
8  *
9  * Permission is granted to anyone to use this software for any purpose,
10  * including commercial applications, and to alter it and redistribute it
11  * freely, subject to the following restrictions:
12  *
13  * 1. The origin of this software must not be misrepresented; you must not
14  * claim that you wrote the original software. If you use this software
15  * in a product, an acknowledgment in the product documentation would be
16  * appreciated but is not required.
17  * 2. Altered source versions must be plainly marked as such, and must not be
18  * misrepresented as being the original software.
19  * 3. This notice may not be removed or altered from any source distribution.
20  */
21 
27 #include <algorithm>
28 
29 namespace playrho {
30 
31 const char *GetName(TOIOutput::State state) noexcept
32 {
33  switch (state)
34  {
35  case TOIOutput::e_unknown: break;
36  case TOIOutput::e_touching: return "touching";
37  case TOIOutput::e_separated: return "separated";
38  case TOIOutput::e_overlapped: return "overlapped";
39  case TOIOutput::e_nextAfter: return "next-after";
40  case TOIOutput::e_maxRootIters: return "max-root-iters";
41  case TOIOutput::e_maxToiIters: return "max-toi-iters";
42  case TOIOutput::e_belowMinTarget: return "below-min-target";
43  case TOIOutput::e_maxDistIters: return "max-dist-iters";
44  case TOIOutput::e_targetDepthExceedsTotalRadius: return "target-depth-exceeds-total-radius";
45  case TOIOutput::e_minTargetSquaredOverflow: return "min-target-squared-overflow";
46  case TOIOutput::e_maxTargetSquaredOverflow: return "max-target-squared-overflow";
47  case TOIOutput::e_notFinite: return "not-finite";
48  }
49  assert(state == TOIOutput::e_unknown);
50  return "unknown";
51 }
52 
53 ToiConf GetToiConf(const StepConf& conf) noexcept
54 {
55  return ToiConf{}
56  .UseTimeMax(1)
57  .UseTargetDepth(conf.targetDepth)
58  .UseTolerance(conf.tolerance)
59  .UseMaxRootIters(conf.maxToiRootIters)
60  .UseMaxToiIters(conf.maxToiIters)
61  .UseMaxDistIters(conf.maxDistanceIters);
62 }
63 
64 namespace d2 {
65 
66 TOIOutput GetToiViaSat(const DistanceProxy& proxyA, const Sweep& sweepA,
67  const DistanceProxy& proxyB, const Sweep& sweepB,
68  ToiConf conf)
69 {
70  assert(IsValid(sweepA));
71  assert(IsValid(sweepB));
72  assert(sweepA.GetAlpha0() == sweepB.GetAlpha0());
73  assert(conf.tMax >= 0 && conf.tMax <=1);
74 
75  // CCD via the local separating axis method. This seeks progression
76  // by computing the largest time at which separation is maintained.
77 
78  auto stats = TOIOutput::Statistics{};
79 
80  const auto totalRadius = proxyA.GetVertexRadius() + proxyB.GetVertexRadius();
81  if (conf.targetDepth > totalRadius)
82  {
84  }
85 
86  const auto target = totalRadius - conf.targetDepth;
87  const auto maxTarget = std::max(target + conf.tolerance, 0_m);
88  const auto minTarget = std::max(target - conf.tolerance, 0_m);
89 
90  const auto minTargetSquared = Square(minTarget);
91  if (!isfinite(minTargetSquared) && isfinite(minTarget))
92  {
94  }
95 
96  const auto maxTargetSquared = Square(maxTarget);
97  if (!isfinite(maxTargetSquared) && isfinite(maxTarget))
98  {
100  }
101 
102  auto timeLo = Real{0}; // Will be set to value of timeHi
103  auto timeLoXfA = GetTransformation(sweepA, timeLo);
104  auto timeLoXfB = GetTransformation(sweepB, timeLo);
105 
106  // Prepare input for distance query.
107  auto distanceConf = GetDistanceConf(conf);
108 
109  // The outer loop progressively attempts to compute new separating axes.
110  // This loop terminates when an axis is repeated (no progress is made).
111  while (stats.toi_iters < conf.maxToiIters)
112  {
113  // Get information on the distance between shapes. We can also use the results
114  // to get a separating axis.
115  const auto dinfo = Distance(proxyA, timeLoXfA, proxyB, timeLoXfB, distanceConf);
116  ++stats.toi_iters;
117  stats.sum_dist_iters += dinfo.iterations;
118  stats.max_dist_iters = std::max(stats.max_dist_iters, dinfo.iterations);
119 
120  if (dinfo.state == DistanceOutput::HitMaxIters)
121  {
122  return TOIOutput{timeLo, stats, TOIOutput::e_maxDistIters};
123  }
124  assert(dinfo.state != DistanceOutput::Unknown);
125  distanceConf.cache = Simplex::GetCache(dinfo.simplex.GetEdges());
126 
127  // Get the real distance squared between shapes at the time of timeLo.
128  const auto distSquared = GetMagnitudeSquared(GetDelta(GetWitnessPoints(dinfo.simplex)));
129 #if 0
130  if (!isfinite(distSquared))
131  {
132  return TOIOutput{timeLo, stats, TOIOutput::e_notFinite};
133  }
134 #endif
135  // If shapes closer at time timeLo than min-target squared, bail as overlapped.
136  if (distSquared < minTargetSquared)
137  {
139  return TOIOutput{timeLo, stats, TOIOutput::e_overlapped};
140  }
141 
142  if (distSquared <= maxTargetSquared) // Victory!
143  {
144  // The two convex polygons are within the target range of each other at time timeLo!
145  return TOIOutput{timeLo, stats, TOIOutput::e_touching};
146  }
147 
148  // From here on, the real distance squared at time timeLo is > than maxTargetSquared
149 
150  // Initialize the separating axis.
151  const auto fcn = GetSeparationScenario(distanceConf.cache.indices,
152  proxyA, timeLoXfA, proxyB, timeLoXfB);
153 
154  // Compute the TOI on the separating axis. We do this by successively
155  // resolving the deepest point. This loop is bounded by the number of vertices.
156  auto timeHi = conf.tMax; // timeHi goes to values between timeLo and timeHi.
157  auto timeHiXfA = GetTransformation(sweepA, timeHi);
158  auto timeHiXfB = GetTransformation(sweepB, timeHi);
159 
160  auto pbIter = decltype(MaxShapeVertices){0};
161  for (; pbIter < MaxShapeVertices; ++pbIter)
162  {
163  // Find the deepest point at timeHi. Store the witness point indices.
164  const auto timeHiMinSep = FindMinSeparation(fcn, timeHiXfA, timeHiXfB);
165 
166  // Is the final configuration separated?
167  if (timeHiMinSep.distance > maxTarget)
168  {
169  // Victory! No collision occurs within time span.
170  assert(timeHi == conf.tMax);
171  // Formerly this used tMax as in...
172  // return TOIOutput{TOIOutput::e_separated, tMax};
173  // timeHi seems more appropriate however given s2 was derived from it.
174  // Meanwhile timeHi always seems equal to input.tMax at this point.
175  stats.sum_finder_iters += pbIter;
176  return TOIOutput{timeHi, stats, TOIOutput::e_separated};
177  }
178 
179  // From here on, timeHiMinSep.distance <= maxTarget
180 
181  // Has the separation reached tolerance?
182  if (timeHiMinSep.distance >= minTarget)
183  {
184  if (timeHi == timeLo)
185  {
186  //
187  // Can't advance timeLo since timeHi already the same.
188  //
189  // This state happens when the real distance is greater than maxTarget but the
190  // timeHiMinSep distance is less than maxTarget. If function not stopped,
191  // it runs till stats.toi_iters == conf.maxToiIters and returns a failed state.
192  // Given that the function can't advance anymore, there's no need to run
193  // anymore. Additionally, given that timeLo is the same as timeHi and the real
194  // distance is separated, this function can return the separated state.
195  //
196  stats.sum_finder_iters += pbIter;
197  return TOIOutput{timeHi, stats, TOIOutput::e_separated};
198  }
199 
200  // Advance the sweeps
201  timeLo = timeHi;
202  timeLoXfA = timeHiXfA;
203  timeLoXfB = timeHiXfB;
204  break;
205  }
206 
207  // From here on timeHiMinSep.distance is < minTarget; i.e. at timeHi, shapes too close.
208 
209  // Compute the initial separation of the witness points.
210  const auto timeLoEvalDistance = Evaluate(fcn, timeLoXfA, timeLoXfB,
211  timeHiMinSep.indices);
212 
213  // Check for initial overlap. Might happen if root finder runs out of iterations.
214  //assert(s1 >= minTarget);
215  // Check for touching
216  if (timeLoEvalDistance <= maxTarget)
217  {
218  if (timeLoEvalDistance < minTarget)
219  {
220  stats.sum_finder_iters += pbIter;
221  return TOIOutput{timeLo, stats, TOIOutput::e_belowMinTarget};
222  }
223  // Victory! timeLo should hold the TOI (could be 0.0).
224  stats.sum_finder_iters += pbIter;
225  return TOIOutput{timeLo, stats, TOIOutput::e_touching};
226  }
227 
228  // Now: timeLoEvalDistance > maxTarget
229 
230  // Compute 1D root of: f(t) - target = 0
231  auto a1 = timeLo;
232  auto a2 = timeHi;
233  auto s1 = timeLoEvalDistance;
234  auto s2 = timeHiMinSep.distance;
235  auto roots = decltype(conf.maxRootIters){0}; // counts # times f(t) checked
236  auto t = timeLo;
237  for (;;)
238  {
239  assert(!AlmostZero(s2 - s1));
240  assert(a1 <= a2);
241 
242  auto state = TOIOutput::e_unknown;
243  if (roots == conf.maxRootIters)
244  {
246  }
247  else if (nextafter(a1, a2) >= a2)
248  {
249  state = TOIOutput::e_nextAfter;
250  }
251  if (state != TOIOutput::e_unknown)
252  {
253  // Reached max root iterations or...
254  // Reached the limit of the Real type's precision!
255  // In this state, there's no way to make progress anymore.
256  // (a1 + a2) / 2 results in a1! So bail from function.
257  stats.sum_finder_iters += pbIter;
258  stats.sum_root_iters += roots;
259  stats.max_root_iters = std::max(stats.max_root_iters, roots);
260  return TOIOutput{t, stats, state};
261  }
262 
263  // Uses secant to improve convergence & bisection to guarantee progress.
264  t = IsOdd(roots)? Secant(target, a1, s1, a2, s2): Bisect(a1, a2);
265 
266  // Using secant method, t may equal a2 now.
267  //assert(t != a1);
268  ++roots;
269 
270  // If t == a1 or t == a2 then, there's a precision/rounding problem.
271  // Accept that for now and keep going...
272 
273  const auto txfA = GetTransformation(sweepA, t);
274  const auto txfB = GetTransformation(sweepB, t);
275  const auto s = Evaluate(fcn, txfA, txfB, timeHiMinSep.indices);
276 
277  if (abs(s - target) <= conf.tolerance) // Root finding succeeded!
278  {
279  assert(t != timeHi);
280  timeHi = t; // timeHi holds a tentative value for timeLo
281  timeHiXfA = txfA;
282  timeHiXfB = txfB;
283  break; // leave before roots can be == conf.maxRootIters
284  }
285 
286  // Ensure we continue to bracket the root.
287  if (s > target)
288  {
289  a1 = t;
290  s1 = s;
291  }
292  else // s <= target
293  {
294  a2 = t;
295  s2 = s;
296  }
297  }
298 
299  // Found a new timeHi: timeHi, timeHiXfA, and timeHiXfB have been updated.
300  stats.sum_root_iters += roots;
301  stats.max_root_iters = std::max(stats.max_root_iters, roots);
302  }
303  stats.sum_finder_iters += pbIter;
304  }
305 
306  // stats.toi_iters == conf.maxToiIters
307  // Root finder got stuck.
308  // This can happen if the two shapes never actually collide within their sweeps.
309  return TOIOutput{timeLo, stats, TOIOutput::e_maxToiIters};
310 }
311 
312 } // namespace d2
313 } // namespace playrho