Loading [MathJax]/extensions/TeX/AMSmath.js
Radium Engine  1.5.0
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Modules Pages
RayCast.cpp
1 #include <Core/Geometry/RayCast.hpp>
2 #include <Core/Geometry/TriangleMesh.hpp>
3 #include <Core/Math/LinearAlgebra.hpp> // Math::sign
4 
5 namespace Ra {
6 namespace Core {
7 // useful : http://www.realtimerendering.com/intersections.html
8 namespace Geometry {
10 bool RayCastAabb( const Ray& r, const Core::Aabb& aabb, Scalar& hitOut, Vector3& normalOut ) {
11  // Based on optimized Woo version (ray vs 3 slabs)
12  // Ref : Graphics Gems p.395
13  // http://www.codercorner.com/RayAABB.cpp
14 
15  CORE_ASSERT( r.direction().squaredNorm() > 0.f, "Invalid Ray" );
16  CORE_ASSERT( !aabb.isEmpty(), "Empty AABB" ); // Or return false ?
17 
18  // Vector of bool telling which components of the direction are not 0;
19  auto nEqualZero = r.direction().array() != Core::Vector3::Zero().array();
20 
21  // Vector of bool telling which components of the ray origin are respectively
22  // smaller than the aabb min or higher than aabb max.
23  auto infMin = r.origin().array() < aabb.min().array();
24  auto supMax = r.origin().array() > aabb.max().array();
25 
26  // Get rid of the case where the origin of the ray is inside the box
27  if ( !( infMin.any() ) && !( supMax.any() ) ) {
28  hitOut = 0;
29  normalOut = -r.direction();
30  return true;
31  }
32 
33  // Precompute the t values for each plane.
34  const Core::Vector3 invDir = r.direction().cwiseInverse();
35  const Core::Vector3 minOrig = ( aabb.min() - r.origin() ).cwiseProduct( invDir );
36  const Core::Vector3 maxOrig = ( aabb.max() - r.origin() ).cwiseProduct( invDir );
37 
38  // The following Eigen dark magic should be equivalent to this pseudo code
39  // For i = 1..3
40  // if (direction[i] !=0)
41  // if (origin[i] < aabb.min) then maxT[i] = (min[i] - origin[i]) / direction[i])
42  // else if (origin[i] > aabb.max) then maxT[i] = (max[i] - origin[i]) / direction[i]
43 
44  auto maxT = nEqualZero.select(
45  infMin.select( minOrig.array(),
46  supMax.select( maxOrig.array(), -std::numeric_limits<Scalar>::max() ) ),
47  -std::numeric_limits<Scalar>::max() );
48 
49  // Find candidate t.
50  uint i;
51  const Scalar t = maxT.maxCoeff( &i );
52 
53  // Check if the point is actually in the box's face.
54  const Vector3 p = r.pointAt( t );
55  const Vector3 s = Vector3::Unit( i );
56 
57  auto inFace =
58  s.select( 0, ( p.array() < aabb.min().array() || p.array() > aabb.max().array() ) );
59 
60  // Ignore negative t (box behind the origin), and points outside the aabb.
61  if ( t >= 0 && !inFace.any() ) {
62  hitOut = t;
63  normalOut = -s * Math::sign( r.direction()[i] );
64  return true;
65  }
66  return false;
67 }
68 
69 bool RayCastSphere( const Ray& r,
70  const Core::Vector3& center,
71  Scalar radius,
72  std::vector<Scalar>& hitsOut ) {
73 
74  CORE_ASSERT( r.direction().squaredNorm() > 0.f, "Invalid Ray" );
75  CORE_ASSERT( radius > 0.f, "Invalid radius" );
76 
77  // Solve a 2nd degree eqn. in t
78  // X = ray.origin + t* ray.direction
79  // ||X - center|| = radius.
80 
81  const Core::Vector3 co = r.origin() - center;
82  const Scalar co2 = co.squaredNorm();
83  const Scalar dirDotCO = r.direction().dot( co );
84 
85  // t is one solution of at^2 + bt + c = 0;
86  // with a = || direction || ^2 = 1.f (rays are normalized in Eigen)
87  const Scalar c = co2 - ( radius * radius );
88  const Scalar b = 2.f * dirDotCO;
89 
90  const Scalar delta = ( b * b ) - ( 4.f * c );
91 
92  if ( delta == 0.f ) {
93  const Scalar t = -b * 0.5f;
94  const bool tPositive = ( t >= 0.f );
95  if ( tPositive ) { hitsOut.push_back( t ); }
96  return tPositive;
97  }
98  else if ( delta > 0.f ) {
99  const Scalar t1 = ( -b - std::sqrt( delta ) ) * 0.5f;
100  const Scalar t2 = ( -b + std::sqrt( delta ) ) * 0.5f;
101 
102  // We know this because a is > 0;
103  CORE_ASSERT( t1 < t2, "Your math is wrong." );
104 
105  const bool t1Positive = ( t1 >= 0.f );
106  const bool t2Positive = ( t2 >= 0.f );
107  if ( t1Positive ) { hitsOut.push_back( t1 ); }
108  if ( t2Positive ) { hitsOut.push_back( t2 ); }
109 
110  return ( t1Positive || t2Positive );
111  }
112  return false;
113 }
114 
115 bool RayCastPlane( const Ray& r,
116  const Core::Vector3& a,
117  const Core::Vector3& normal,
118  std::vector<Scalar>& hitsOut ) {
119  CORE_ASSERT( r.direction().squaredNorm() > 0.f, "Invalid Ray" );
120  CORE_ASSERT( normal.squaredNorm() > 0.f, "Invalid plane normal" );
121 
122  // Solve for t the first order eqn.
123  // P = O + t. d
124  // AP . n = 0
125  // gives t = (d.n / OA.n)
126 
127  const Scalar ddotn = r.direction().dot( normal );
128  const Scalar OAdotn = ( a - r.origin() ).dot( normal );
129 
130  // If d.n is non zero, the line intersects the plane.
131  // we check that the ray intersects for t>=0 by checking that d.n and OA.n have the same sign.
132  if ( ddotn != 0 && ( ddotn * OAdotn ) >= 0 ) {
133  hitsOut.push_back( OAdotn / ddotn );
134  return true;
135  }
136  // If d.n is 0 the ray is parallel to the plane, so there is only an intersection
137  // if the ray is completely in the plane (i.e. if OA.n = 0).
138  else if ( ddotn == 0 && OAdotn == 0 ) {
139  hitsOut.push_back( 0 );
140  return true;
141  }
142 
143  return false;
144 }
145 
146 // TODO : this needs serious optimizing if we want it fast :p
147 bool RayCastCylinder( const Ray& r,
148  const Core::Vector3& a,
149  const Core::Vector3& b,
150  Scalar radius,
151  std::vector<Scalar>& hitsOut ) {
154  const Scalar radiusSquared = radius * radius;
155 
156  const Core::Vector3 cylAxis = b - a;
157  const Core::Vector3 ao = r.origin() - a;
158 
159  // Intersect the ray against plane A and B.
160  std::vector<Scalar> hitsA;
161  const bool vsA = RayCastPlane( r, a, cylAxis, hitsA );
162  const Scalar hitA = vsA ? hitsA[0] : -1.f;
163 
164  std::vector<Scalar> hitsB;
165  const bool vsB = RayCastPlane( r, b, cylAxis, hitsB );
166  const Scalar hitB = vsB ? hitsB[0] : -1.f;
167 
168  auto n = r.direction().cross( cylAxis );
169  // Degenerated case : cylinder axis parallel to ray.
170  if ( UNLIKELY( n.squaredNorm() == 0 ) ) {
171  // Distance between two parallel lines.
172  const Scalar distSquared =
173  ao.cross( r.direction() ).squaredNorm() / r.direction().squaredNorm();
174 
175  // Is the ray inside the cylinder ?
176  if ( distSquared <= ( radiusSquared ) ) {
177  // In this case we must hit at least one of the planes
178  CORE_ASSERT( vsA || vsB, "Ray must hit at least one of the planes !" );
179 
180  // The most common case is that both plane are hits (i.e. the ray's origin is outside
181  // the cylinder). In that case we just return the smallest hit.
182  if ( LIKELY( vsA && vsB ) ) {
183  CORE_ASSERT( std::min( hitA, hitB ) >= 0, "Invalid hit result" );
184  hitsOut.push_back( std::min( hitA, hitB ) );
185  return true;
186  }
187 
188  // If only one plane is hit, then the ray is inside the cylinder, so we return the ray
189  // origin as the result point.
190  hitsOut.push_back( 0 );
191  return true;
192  }
193  // Ray is outside the diameter, so the result is a miss.
194  return false;
195  }
196  else // Ray not parallel to the cylinder. We compute the ray/infinite cylinder intersection
197  {
198  const Scalar ln = n.norm();
199  n.normalize();
200 
201  // Distance between two skew lines ray and cylinder axis.
202  const Scalar dist = std::abs( ao.dot( n ) );
203 
204  // If the distance is bigger than radius, no further processing is needed
205  // and it's an early exit. If not then an intersection with the infinite
206  // cylinder is guaranteed.
207  if ( dist <= radius ) {
208  // TODO : this could be optimized with squared norms.
209  auto v1 = cylAxis.cross( ao );
210  const Scalar t = v1.dot( n ) / ln;
211  auto v2 = n.cross( cylAxis ).normalized();
212  const Scalar s =
213  std::sqrt( radiusSquared - ( dist * dist ) ) / std::abs( r.direction().dot( v2 ) );
214 
215  Scalar tIn = t - s;
216  Scalar tOut = t + s;
217  CORE_ASSERT( tIn <= tOut, " " );
218 
219  // Now clip the ray along planes. (TODO : refactor plane clipping with vsPlane ?)
220 
221  const Scalar ddotAxis = r.direction().dot( cylAxis );
222 
223  // Ray has an opposite direction cylinder axis, so it may enter through plane B and exit
224  // through plane A.
225  if ( ddotAxis < 0 ) {
226  const Scalar tInPlaneB = ( b - r.origin() ).dot( cylAxis ) / ddotAxis;
227  const Scalar tOutPlaneA = ( a - r.origin() ).dot( cylAxis ) / ddotAxis;
228 
229  // Early exit condition if the ray misses the capped cylinder
230  if ( tInPlaneB > tOut || tOutPlaneA < tIn ) { return false; }
231  // Cap the in and out
232  if ( tInPlaneB > tIn && tInPlaneB < tOut ) { tIn = tInPlaneB; }
233  if ( tOutPlaneA > tIn && tOutPlaneA < tOut ) { tOut = tOutPlaneA; }
234  }
235  // Ray has the same direction as the cylinder axis it may enter through plane A and exit
236  // through B.
237  else if ( ddotAxis > 0 ) {
238  const Scalar tInPlaneA = ( a - r.origin() ).dot( cylAxis ) / ddotAxis;
239  const Scalar tOutPlaneB = ( b - r.origin() ).dot( cylAxis ) / ddotAxis;
240 
241  // Early exit condition if the ray misses the capped cylinder
242  if ( tInPlaneA > tOut || tOutPlaneB < tIn ) { return false; }
243  // Cap the in and out
244  if ( tInPlaneA > tIn && tInPlaneA < tOut ) { tIn = tInPlaneA; }
245 
246  if ( tOutPlaneB > tIn && tOutPlaneB < tOut ) { tOut = tOutPlaneB; }
247  }
248  // Degenerate case : ray parallel to end planes.
249  // in that case we check that the ray does not miss the cylinder.
250  else if ( UNLIKELY( ddotAxis == 0 ) ) {
251  const Scalar h = ao.dot( cylAxis );
252  const Scalar lAxisSq = cylAxis.squaredNorm();
253  if ( h < 0 || h > lAxisSq ) { return false; }
254  }
255 
256  // At this point our tIn and tOut are valid within the capped cylinder.
257  // The only thing left is to find whether the ray hits (i.e. tIn is positive).
258  if ( tIn >= 0 ) {
259  hitsOut.push_back( tIn );
260  return true;
261  }
262  // tIn is negative but tOut is positive, which means the origin is inside the cylinder,
263  // so we return 0
264  else if ( tIn * tOut < 0 ) {
265  hitsOut.push_back( 0 );
266  return true;
267  }
268  } // End if (distance between ray and cyl axis < radius)
269  } // End of else (ray not parallel to the cylinder.
270 
271  return false;
272 }
273 
274 bool RayCastTriangle( const Ray& ray,
275  const Vector3& a,
276  const Vector3& b,
277  const Vector3& c,
278  std::vector<Scalar>& hitsOut ) {
279  const Vector3 ab = b - a;
280  const Vector3 ac = c - a;
281 
282  ON_ASSERT( const Vector3 n = ab.cross( ac ) );
283  CORE_ASSERT( n.squaredNorm() > 0, "Degenerate triangle" );
284 
285  // Compute determinant
286  const Vector3 pvec = ray.direction().cross( ac );
287  const Scalar det = ab.dot( pvec );
288 
289  const Vector3 tvec = ray.origin() - a;
290  const Scalar inv_det = 1.f / det;
291 
292  const Vector3 qvec = tvec.cross( ab );
293 
294  if ( det > 0 ) {
295  Scalar u = tvec.dot( pvec );
296  if ( u < 0 || u > det ) {
297  return false; // We're out of the slab across ab
298  }
299 
300  Scalar v = ray.direction().dot( qvec );
301  if ( v < 0 || u + v > det ) { return false; }
302  }
303  else if ( det < 0 ) {
304  Scalar u = tvec.dot( pvec );
305  if ( u > 0 || u < det ) { return false; }
306 
307  Scalar v = ray.direction().dot( qvec );
308  if ( v > 0 || u + v < det ) { return false; }
309  }
310  else // line parallel to plane. Maybe we should intersect with the triangle edges ?
311  {
312  return false;
313  }
314 
315  // If we're here we really intersect the triangle so let's compute T.
316  const Scalar t = ac.dot( qvec ) * inv_det;
317  if ( t >= 0 ) { hitsOut.push_back( t ); }
318  return ( t >= 0 );
319 }
320 
321 bool RayCastTriangleMesh( const Ray& r,
322  const TriangleMesh& mesh,
323  std::vector<Scalar>& hitsOut,
324  std::vector<Vector3ui>& trianglesIdxOut ) {
325  bool hit = false;
326  for ( size_t i = 0; i < mesh.getIndices().size(); ++i ) {
327  const auto& t = mesh.getIndices()[i];
328  const Vector3& a = mesh.vertices()[t[0]];
329  const Vector3& b = mesh.vertices()[t[1]];
330  const Vector3& c = mesh.vertices()[t[2]];
331  if ( RayCastTriangle( r, a, b, c, hitsOut ) ) {
332  trianglesIdxOut.push_back( t );
333  hit = true;
334  }
335  }
336 
337  return hit;
338 }
339 } // namespace Geometry
340 } // namespace Core
341 } // namespace Ra
constexpr int sign(const T &val)
Returns the sign of any numeric type as { -1, 0, 1}.
Definition: Math.hpp:106
Definition: Cage.cpp:3