Radium Engine  1.5.20
Loading...
Searching...
No Matches
RayCast.cpp
1#include <Core/Geometry/RayCast.hpp>
2#include <Core/Geometry/TriangleMesh.hpp>
3#include <Core/Math/LinearAlgebra.hpp> // Math::sign
4
5namespace Ra {
6namespace Core {
7// useful : http://www.realtimerendering.com/intersections.html
8namespace Geometry {
10bool 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() ) ),
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
69bool 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
115bool 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
147bool 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.
161 const bool vsA = RayCastPlane( r, a, cylAxis, hitsA );
162 const Scalar hitA = vsA ? hitsA[0] : -1.f;
163
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
274bool 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
321bool 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
T min(T... args)
constexpr int sign(const T &val)
Returns the sign of any numeric type as { -1, 0, 1}.
Definition Math.hpp:106
@ Geometry
"Geometry" render objects are those loaded using Radium::IO and generated by GeometrySystem
hepler function to manage enum as underlying types in VariableSet
Definition Cage.cpp:3
T push_back(T... args)
T sqrt(T... args)