RayCastOutput.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  * Permission is granted to anyone to use this software for any purpose,
9  * including commercial applications, and to alter it and redistribute it
10  * freely, subject to the following restrictions:
11  * 1. The origin of this software must not be misrepresented; you must not
12  * claim that you wrote the original software. If you use this software
13  * in a product, an acknowledgment in the product documentation would be
14  * appreciated but is not required.
15  * 2. Altered source versions must be plainly marked as such, and must not be
16  * misrepresented as being the original software.
17  * 3. This notice may not be removed or altered from any source distribution.
18  */
19 
20 #include <PlayRho/Common/Math.hpp>
29 #include <utility>
30 
31 namespace playrho {
32 namespace d2 {
33 
34 RayCastOutput RayCast(Length radius, Length2 location, const RayCastInput& input) noexcept
35 {
36  // Collision Detection in Interactive 3D Environments by Gino van den Bergen
37  // From Section 3.1.2
38  // x = s + a * r
39  // norm(x) = radius
40 
41  const auto s = input.p1 - location;
42  const auto b = GetMagnitudeSquared(s) - Square(radius);
43 
44  // Solve quadratic equation.
45  const auto raySegment = input.p2 - input.p1; // Length2
46  const auto c = Dot(s, raySegment); // Area
47  const auto rr = GetMagnitudeSquared(raySegment); // Area
48  const auto sigma = Real{(Square(c) - rr * b) / (SquareMeter * SquareMeter)};
49 
50  // Check for negative discriminant and short segment.
51  if ((sigma < Real{0}) || AlmostZero(Real{rr / SquareMeter}))
52  {
53  return RayCastOutput{};
54  }
55 
56  // Find the point of intersection of the line with the circle.
57  const auto a = -(c + sqrt(sigma) * SquareMeter);
58  const auto fraction = Real{a / rr};
59 
60  // Is the intersection point on the segment?
61  if ((fraction >= Real{0}) && (fraction <= input.maxFraction))
62  {
63  const auto normal = GetUnitVector(s + fraction * raySegment, UnitVec::GetZero());
64  return RayCastOutput{{normal, fraction}};
65  }
66 
67  return RayCastOutput{};
68 }
69 
70 RayCastOutput RayCast(const AABB& aabb, const RayCastInput& input) noexcept
71 {
72  // From Real-time Collision Detection, p179.
73 
74  auto normal = UnitVec{};
75  auto tmin = -MaxFloat;
76  auto tmax = MaxFloat;
77 
78  const auto p1 = input.p1;
79  const auto pDelta = input.p2 - input.p1;
80  for (auto i = decltype(pDelta.max_size()){0}; i < pDelta.max_size(); ++i)
81  {
82  const auto p1i = p1[i];
83  const auto pdi = pDelta[i];
84  const auto range = aabb.ranges[i];
85 
86  if (AlmostZero(pdi))
87  {
88  // Parallel.
89  if ((p1i < range.GetMin()) || (p1i > range.GetMax()))
90  {
91  return RayCastOutput{};
92  }
93  }
94  else
95  {
96  const auto reciprocalOfPdi = Real{1} / pdi;
97  auto t1 = Real{(range.GetMin() - p1i) * reciprocalOfPdi};
98  auto t2 = Real{(range.GetMax() - p1i) * reciprocalOfPdi};
99  auto s = -1; // Sign of the normal vector.
100  if (t1 > t2)
101  {
102  swap(t1, t2);
103  s = 1;
104  }
105  if (tmin < t1)
106  {
107  normal = (i == 0)?
108  ((s < 0)? UnitVec::GetLeft(): UnitVec::GetRight()):
109  ((s < 0)? UnitVec::GetBottom(): UnitVec::GetTop());
110  tmin = t1; // Push the min up
111  }
112  tmax = std::min(tmax, t2); // Pull the max down
113  if (tmin > tmax)
114  {
115  return RayCastOutput{};
116  }
117  }
118  };
119 
120  // Does the ray start inside the box?
121  // Does the ray intersect beyond the max fraction?
122  if ((tmin < 0) || (tmin > input.maxFraction))
123  {
124  return RayCastOutput{};
125  }
126 
127  // Intersection.
128  return RayCastOutput{{normal, tmin}};
129 }
130 
131 RayCastOutput RayCast(const DistanceProxy& proxy, const RayCastInput& input,
132  const Transformation& transform) noexcept
133 {
134  const auto vertexCount = proxy.GetVertexCount();
135  assert(vertexCount > 0);
136 
137  const auto radius = proxy.GetVertexRadius();
138  auto v0 = proxy.GetVertex(0);
139  if (vertexCount == 1)
140  {
141  return RayCast(radius, Transform(v0, transform), input);
142  }
143 
144  // Uses algorithm described at http://stackoverflow.com/a/565282/7410358
145  //
146  // The SO author gave the algorithm the following credit:
147  // "Intersection of two lines in three-space" by Ronald Goldman,
148  // published in Graphics Gems, page 304.
149 
150  // Solve for p + t r = q + u s
151 
152  // p is input.p1
153  // q is the offset vertex
154  // s is vertexDelta
155  // r is rayDelta
156  // t = (q - p) x s / (r x s)
157  // u = (q - p) x r / (r x s)
158 
159  // Put the ray into the polygon's frame of reference.
160  const auto transformedInput = RayCastInput{
161  InverseTransform(input.p1, transform),
162  InverseTransform(input.p2, transform),
163  input.maxFraction
164  };
165  const auto ray0 = transformedInput.p1;
166  const auto ray = transformedInput.p2 - transformedInput.p1; // Ray delta (p2 - p1)
167 
168  auto minT = nextafter(Real{input.maxFraction}, Real(2));
169  auto normalFound = GetInvalid<UnitVec>();
170 
171  for (auto i = decltype(vertexCount){0}; i < vertexCount; ++i)
172  {
173  const auto circleResult = RayCast(radius, v0, transformedInput);
174  if (circleResult.has_value() && (minT > circleResult->fraction))
175  {
176  minT = circleResult->fraction;
177  normalFound = circleResult->normal;
178  }
179 
180  const auto v1 = proxy.GetVertex(GetModuloNext(i, vertexCount));
181  const auto edge = v1 - v0; // Vertex delta
182  const auto ray_cross_edge = Cross(ray, edge);
183 
184  if (!AlmostZero(Real{ray_cross_edge / SquareMeter}))
185  {
186  const auto normal = proxy.GetNormal(i);
187  const auto offset = normal * radius;
188  const auto v0off = v0 + offset;
189  const auto q_sub_p = v0off - ray0;
190 
191  const auto reciprocalRayCrossEdge = Real{1} / ray_cross_edge;
192 
193  // t = ((q - p) x s) / (r x s)
194  const auto t = Cross(q_sub_p, edge) * reciprocalRayCrossEdge;
195 
196  // u = ((q - p) x r) / (r x s)
197  const auto u = Cross(q_sub_p, ray) * reciprocalRayCrossEdge;
198 
199  if ((t >= 0) && (t <= 1) && (u >= 0) && (u <= 1))
200  {
201  // The two lines meet at the point p + t r = q + u s
202  if (minT > t)
203  {
204  minT = t;
205  normalFound = normal;
206  }
207  }
208  else
209  {
210  // The two line segments are not parallel but do not intersect.
211  }
212  }
213  else
214  {
215  // The two lines are parallel, ignored.
216  }
217 
218  v0 = v1;
219  }
220 
221  if (minT <= input.maxFraction)
222  {
223  return RayCastOutput{{Rotate(normalFound, transform.q), minT}};
224  }
225  return RayCastOutput{};
226 }
227 
228 RayCastOutput RayCast(const Shape& shape, ChildCounter childIndex,
229  const RayCastInput& input, const Transformation& transform) noexcept
230 {
231  return RayCast(GetChild(shape, childIndex), input, transform);
232 }
233 
234 bool RayCast(const DynamicTree& tree, RayCastInput input, const DynamicTreeRayCastCB& callback)
235 {
236  const auto v = GetRevPerpendicular(GetUnitVector(input.p2 - input.p1, UnitVec::GetZero()));
237  const auto abs_v = abs(v);
238  auto segmentAABB = d2::GetAABB(input);
239 
241  stack.push(tree.GetRootIndex());
242  while (!empty(stack))
243  {
244  const auto index = stack.top();
245  stack.pop();
246  if (index == DynamicTree::GetInvalidSize())
247  {
248  continue;
249  }
250 
251  const auto aabb = tree.GetAABB(index);
252  if (!TestOverlap(aabb, segmentAABB))
253  {
254  continue;
255  }
256 
257  // Separating axis for segment (Gino, p80).
258  // |dot(v, p1 - ctr)| > dot(|v|, extents)
259  const auto center = GetCenter(aabb);
260  const auto extents = GetExtents(aabb);
261  const auto separation = abs(Dot(v, input.p1 - center)) - Dot(abs_v, extents);
262  if (separation > 0_m)
263  {
264  continue;
265  }
266 
267  if (DynamicTree::IsBranch(tree.GetHeight(index)))
268  {
269  const auto branchData = tree.GetBranchData(index);
270  stack.push(branchData.child1);
271  stack.push(branchData.child2);
272  }
273  else
274  {
275  assert(DynamicTree::IsLeaf(tree.GetHeight(index)));
276  const auto leafData = tree.GetLeafData(index);
277  const auto value = callback(leafData.fixture, leafData.childIndex, input);
278  if (value == 0)
279  {
280  return true; // Callback has terminated the ray cast.
281  }
282  if (value > 0)
283  {
284  // Update segment bounding box.
285  input.maxFraction = value;
286  segmentAABB = d2::GetAABB(input);
287  }
288  }
289  }
290  return false;
291 }
292 
293 bool RayCast(const DynamicTree& tree, const RayCastInput& rci, FixtureRayCastCB callback)
294 {
295  return RayCast(tree, rci, [callback](Fixture* fixture, ChildCounter index, const RayCastInput& input) {
296  const auto output = RayCast(GetChild(fixture->GetShape(), index), input,
297  fixture->GetBody()->GetTransformation());
298  if (output.has_value())
299  {
300  const auto fraction = output->fraction;
301  assert(fraction >= 0 && fraction <= 1);
302 
303  // Here point can be calculated these two ways:
304  // (1) point = p1 * (1 - fraction) + p2 * fraction
305  // (2) point = p1 + (p2 - p1) * fraction.
306  //
307  // The first way however suffers from the fact that:
308  // a * (1 - fraction) + a * fraction != a
309  // for all values of a and fraction between 0 and 1 when a and fraction are
310  // floating point types.
311  // This leads to the posibility that (p1 == p2) && (point != p1 || point != p2),
312  // which may be pretty surprising to the callback. So this way SHOULD NOT be used.
313  //
314  // The second way, does not have this problem.
315  //
316  const auto point = input.p1 + (input.p2 - input.p1) * fraction;
317  const auto opcode = callback(fixture, index, point, output->normal);
318  switch (opcode)
319  {
320  case RayCastOpcode::Terminate: return Real{0};
321  case RayCastOpcode::IgnoreFixture: return Real{-1};
322  case RayCastOpcode::ClipRay: return Real{fraction};
323  case RayCastOpcode::ResetRay: return Real{input.maxFraction};
324  }
325  }
326  return Real{input.maxFraction};
327  });
328 }
329 
330 } // namespace d2
331 } // namespace playrho