1 
2 //
3 // This source file is part of appleseed.
4 // Visit https://appleseedhq.net/ for additional information and resources.
5 //
6 // This software is released under the MIT license.
7 //
8 // Copyright (c) 2018 Francois Beaune, The appleseedhq Organization
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining a copy
11 // of this software and associated documentation files (the "Software"), to deal
12 // in the Software without restriction, including without limitation the rights
13 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14 // copies of the Software, and to permit persons to whom the Software is
15 // furnished to do so, subject to the following conditions:
16 //
17 // The above copyright notice and this permission notice shall be included in
18 // all copies or substantial portions of the Software.
19 //
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
26 // THE SOFTWARE.
27 //
28 
29 // appleseed.foundation headers.
30 #include "foundation/math/fp.h"
31 #include "foundation/math/intersection/raysphere.h"
32 #include "foundation/math/ray.h"
33 #include "foundation/math/vector.h"
34 #include "foundation/utility/test.h"
35 
36 // Standard headers.
37 #include <cstddef>
38 
39 using namespace foundation;
40 
41 namespace
42 {
43     const Vector3d SphereCenter(6.0, 4.0, 0.0);
44     const double SphereRadius = 2.0;
45 
46     const Vector3d RayOrigin(2.0, 1.0, 0.0);
47     const Vector3d RayDirection(2.0, 1.0, 0.0);     // not unit-length
48 }
49 
TEST_SUITE(Foundation_Math_Intersection_RaySphere)50 TEST_SUITE(Foundation_Math_Intersection_RaySphere)
51 {
52     //
53     // The ray misses the sphere.
54     //
55 
56     TEST_CASE(IntersectSphere_RayMissesSphere_ReturnsFalse)
57     {
58         const Ray3d ray(RayOrigin + Vector3d(6.0, 0.0, 0.0), RayDirection, 1.0, 6.0);
59 
60         const bool hit = intersect_sphere(ray, SphereCenter, SphereRadius);
61 
62         ASSERT_FALSE(hit);
63     }
64 
65     TEST_CASE(IntersectSphere_RayMissesSphere_ReturnsFalseAndLeavesTMinUntouched)
66     {
67         const Ray3d ray(RayOrigin + Vector3d(6.0, 0.0, 0.0), RayDirection, 1.0, 6.0);
68 
69         double tmin = FP<double>::snan();
70         const bool hit = intersect_sphere(ray, SphereCenter, SphereRadius, tmin);
71 
72         ASSERT_FALSE(hit);
73         EXPECT_TRUE(FP<double>::is_snan(tmin));
74     }
75 
76     TEST_CASE(IntersectSphere_RayMissesSphere_ReturnsFalseAndLeavesTOutUntouched)
77     {
78         const Ray3d ray(RayOrigin + Vector3d(6.0, 0.0, 0.0), RayDirection, 1.0, 6.0);
79 
80         double t_out[2] = { FP<double>::snan(), FP<double>::snan() };
81         const size_t hits = intersect_sphere(ray, SphereCenter, SphereRadius, t_out);
82 
83         ASSERT_EQ(0, hits);
84         EXPECT_TRUE(FP<double>::is_snan(t_out[0]));
85         EXPECT_TRUE(FP<double>::is_snan(t_out[1]));
86     }
87 
88     //
89     // The ray's support line intersects the sphere but tmax is too small.
90     //
91 
92     TEST_CASE(IntersectSphere_TMaxTooSmall_ReturnsFalse)
93     {
94         const Ray3d ray(RayOrigin, RayDirection, 0.5, 1.0);
95 
96         const bool hit = intersect_sphere(ray, SphereCenter, SphereRadius);
97 
98         ASSERT_FALSE(hit);
99     }
100 
101     TEST_CASE(IntersectSphere_TMaxTooSmall_ReturnsFalseAndLeavesTMinUntouched)
102     {
103         const Ray3d ray(RayOrigin, RayDirection, 0.5, 1.0);
104 
105         double tmin = FP<double>::snan();
106         const bool hit = intersect_sphere(ray, SphereCenter, SphereRadius, tmin);
107 
108         ASSERT_FALSE(hit);
109         EXPECT_TRUE(FP<double>::is_snan(tmin));
110     }
111 
112     TEST_CASE(IntersectSphere_TMaxTooSmall_ReturnsFalseAndLeavesTOutUntouched)
113     {
114         const Ray3d ray(RayOrigin, RayDirection, 0.5, 1.0);
115 
116         double t_out[2] = { FP<double>::snan(), FP<double>::snan() };
117         const size_t hits = intersect_sphere(ray, SphereCenter, SphereRadius, t_out);
118 
119         ASSERT_EQ(0, hits);
120         EXPECT_TRUE(FP<double>::is_snan(t_out[0]));
121         EXPECT_TRUE(FP<double>::is_snan(t_out[1]));
122     }
123 
124     //
125     // The ray's support line intersects the sphere but tmin is too big.
126     //
127 
128     TEST_CASE(IntersectSphere_TMinTooBig_ReturnsFalse)
129     {
130         const Ray3d ray(RayOrigin, RayDirection, 6.0, 8.0);
131 
132         const bool hit = intersect_sphere(ray, SphereCenter, SphereRadius);
133 
134         ASSERT_FALSE(hit);
135     }
136 
137     TEST_CASE(IntersectSphere_TMinTooBig_ReturnsFalseAndLeavesTMinUntouched)
138     {
139         const Ray3d ray(RayOrigin, RayDirection, 6.0, 8.0);
140 
141         double tmin = FP<double>::snan();
142         const bool hit = intersect_sphere(ray, SphereCenter, SphereRadius, tmin);
143 
144         ASSERT_FALSE(hit);
145         EXPECT_TRUE(FP<double>::is_snan(tmin));
146     }
147 
148     TEST_CASE(IntersectSphere_TMinTooBig_ReturnsFalseAndLeavesTOutUntouched)
149     {
150         const Ray3d ray(RayOrigin, RayDirection, 6.0, 8.0);
151 
152         double t_out[2] = { FP<double>::snan(), FP<double>::snan() };
153         const size_t hits = intersect_sphere(ray, SphereCenter, SphereRadius, t_out);
154 
155         ASSERT_EQ(0, hits);
156         EXPECT_TRUE(FP<double>::is_snan(t_out[0]));
157         EXPECT_TRUE(FP<double>::is_snan(t_out[1]));
158     }
159 
160     //
161     // The ray's support line intersects the sphere but tmin and tmax are inside the sphere.
162     //
163 
164     TEST_CASE(IntersectSphere_TMinAndTMaxInsideSphere_ReturnsFalse)
165     {
166         const Ray3d ray(RayOrigin, RayDirection, 2.0, 3.0);
167 
168         const bool hit = intersect_sphere(ray, SphereCenter, SphereRadius);
169 
170         ASSERT_FALSE(hit);
171     }
172 
173     TEST_CASE(IntersectSphere_TMinAndTMaxInsideSphere_ReturnsFalseAndLeavesTMinUntouched)
174     {
175         const Ray3d ray(RayOrigin, RayDirection, 2.0, 3.0);
176 
177         double tmin = FP<double>::snan();
178         const bool hit = intersect_sphere(ray, SphereCenter, SphereRadius, tmin);
179 
180         ASSERT_FALSE(hit);
181         EXPECT_TRUE(FP<double>::is_snan(tmin));
182     }
183 
184     TEST_CASE(IntersectSphere_TMinAndTMaxInsideSphere_ReturnsFalseAndLeavesTOutUntouched)
185     {
186         const Ray3d ray(RayOrigin, RayDirection, 2.0, 3.0);
187 
188         double t_out[2] = { FP<double>::snan(), FP<double>::snan() };
189         const size_t hits = intersect_sphere(ray, SphereCenter, SphereRadius, t_out);
190 
191         ASSERT_EQ(0, hits);
192         EXPECT_TRUE(FP<double>::is_snan(t_out[0]));
193         EXPECT_TRUE(FP<double>::is_snan(t_out[1]));
194     }
195 
196     //
197     // The ray intersects the sphere in a single point because tmin is inside the sphere.
198     //
199 
200     TEST_CASE(IntersectSphere_TMinInsideSphere_ReturnsTrue)
201     {
202         const Ray3d ray(RayOrigin, RayDirection, 2.0, 6.0);
203 
204         const bool hit = intersect_sphere(ray, SphereCenter, SphereRadius);
205 
206         ASSERT_TRUE(hit);
207     }
208 
209     TEST_CASE(IntersectSphere_TMinInsideSphere_ReturnsTrueAndDistanceToClosestHit)
210     {
211         const Ray3d ray(RayOrigin, RayDirection, 2.0, 6.0);
212 
213         double tmin = FP<double>::snan();
214         const bool hit = intersect_sphere(ray, SphereCenter, SphereRadius, tmin);
215 
216         ASSERT_TRUE(hit);
217         EXPECT_FEQ(3.0, tmin);
218     }
219 
220     TEST_CASE(IntersectSphere_TMinInsideSphere_ReturnsTrueAndDistancesToUniqueHit)
221     {
222         const Ray3d ray(RayOrigin, RayDirection, 2.0, 6.0);
223 
224         double t_out[2] = { FP<double>::snan(), FP<double>::snan() };
225         const size_t hits = intersect_sphere(ray, SphereCenter, SphereRadius, t_out);
226 
227         ASSERT_EQ(1, hits);
228         EXPECT_FEQ(3.0, t_out[0]);
229         EXPECT_TRUE(FP<double>::is_snan(t_out[1]));
230     }
231 
232     //
233     // The ray intersects the sphere in a single point because tmax is inside the sphere.
234     //
235 
236     TEST_CASE(IntersectSphere_TMaxInsideSphere_ReturnsTrue)
237     {
238         const Ray3d ray(RayOrigin, RayDirection, 1.0, 2.0);
239 
240         const bool hit = intersect_sphere(ray, SphereCenter, SphereRadius);
241 
242         ASSERT_TRUE(hit);
243     }
244 
245     TEST_CASE(IntersectSphere_TMaxInsideSphere_ReturnsTrueAndDistanceToClosestHit)
246     {
247         const Ray3d ray(RayOrigin, RayDirection, 1.0, 2.0);
248 
249         double tmin = FP<double>::snan();
250         const bool hit = intersect_sphere(ray, SphereCenter, SphereRadius, tmin);
251 
252         ASSERT_TRUE(hit);
253         EXPECT_FEQ(1.4, tmin);
254     }
255 
256     TEST_CASE(IntersectSphere_TMaxInsideSphere_ReturnsTrueAndDistancesToUniqueHit)
257     {
258         const Ray3d ray(RayOrigin, RayDirection, 1.0, 2.0);
259 
260         double t_out[2] = { FP<double>::snan(), FP<double>::snan() };
261         const size_t hits = intersect_sphere(ray, SphereCenter, SphereRadius, t_out);
262 
263         ASSERT_EQ(1, hits);
264         EXPECT_FEQ(1.4, t_out[0]);
265         EXPECT_TRUE(FP<double>::is_snan(t_out[1]));
266     }
267 
268     //
269     // The ray intersects the sphere in two points.
270     //
271 
272     TEST_CASE(IntersectSphere_RaySegmentsSpansEntireSphere_ReturnsTrue)
273     {
274         const Ray3d ray(RayOrigin, RayDirection, 1.0, 6.0);
275 
276         const bool hit = intersect_sphere(ray, SphereCenter, SphereRadius);
277 
278         ASSERT_TRUE(hit);
279     }
280 
281     TEST_CASE(IntersectSphereUnitDirection_RaySegmentsSpansEntireSphere_ReturnsTrue)
282     {
283         const double K = norm(RayDirection);
284         const Ray3d ray(RayOrigin, RayDirection / K, 1.0 * K, 6.0 * K);
285 
286         const bool hit = intersect_sphere_unit_direction(ray, SphereCenter, SphereRadius);
287 
288         ASSERT_TRUE(hit);
289     }
290 
291     TEST_CASE(IntersectSphere_RaySegmentsSpansEntireSphere_ReturnsTrueAndDistanceToClosestHit)
292     {
293         const Ray3d ray(RayOrigin, RayDirection, 1.0, 6.0);
294 
295         double tmin = FP<double>::snan();
296         const bool hit = intersect_sphere(ray, SphereCenter, SphereRadius, tmin);
297 
298         ASSERT_TRUE(hit);
299         EXPECT_FEQ(1.4, tmin);
300     }
301 
302     TEST_CASE(IntersectSphereUnitDirection_RaySegmentsSpansEntireSphere_ReturnsTrueAndDistanceToClosestHit)
303     {
304         const double K = norm(RayDirection);
305         const Ray3d ray(RayOrigin, RayDirection / K, 1.0 * K, 6.0 * K);
306 
307         double tmin = FP<double>::snan();
308         const bool hit = intersect_sphere(ray, SphereCenter, SphereRadius, tmin);
309 
310         ASSERT_TRUE(hit);
311         EXPECT_FEQ(1.4 * K, tmin);
312     }
313 
314     TEST_CASE(IntersectSphere_RaySegmentsSpansEntireSphere_ReturnsTrueAndDistancesToBothHits)
315     {
316         const Ray3d ray(RayOrigin, RayDirection, 1.0, 6.0);
317 
318         double t_out[2] = { FP<double>::snan(), FP<double>::snan() };
319         const size_t hits = intersect_sphere(ray, SphereCenter, SphereRadius, t_out);
320 
321         ASSERT_EQ(2, hits);
322         EXPECT_FEQ(1.4, t_out[0]);
323         EXPECT_FEQ(3.0, t_out[1]);
324     }
325 
326     TEST_CASE(IntersectSphereUnitDirection_RaySegmentsSpansEntireSphere_ReturnsTrueAndDistancesToBothHits)
327     {
328         const double K = norm(RayDirection);
329         const Ray3d ray(RayOrigin, RayDirection / K, 1.0 * K, 6.0 * K);
330 
331         double t_out[2] = { FP<double>::snan(), FP<double>::snan() };
332         const size_t hits = intersect_sphere(ray, SphereCenter, SphereRadius, t_out);
333 
334         ASSERT_EQ(2, hits);
335         EXPECT_FEQ(1.4 * K, t_out[0]);
336         EXPECT_FEQ(3.0 * K, t_out[1]);
337     }
338 
339     TEST_CASE(IntersectSphere_RaySegmentsSpansEntireSphere_ReversedDirection_ReturnsTrueAndDistancesToBothHits)
340     {
341         const Ray3d ray(2.0 * SphereCenter - RayOrigin, -RayDirection, 1.0, 6.0);
342 
343         double t_out[2] = { FP<double>::snan(), FP<double>::snan() };
344         const size_t hits = intersect_sphere(ray, SphereCenter, SphereRadius, t_out);
345 
346         ASSERT_EQ(2, hits);
347         EXPECT_FEQ(1.4, t_out[0]);
348         EXPECT_FEQ(3.0, t_out[1]);
349     }
350 
351     TEST_CASE(IntersectSphereUnitDirection_RaySegmentsSpansEntireSphere_ReversedDirection_ReturnsTrueAndDistancesToBothHits)
352     {
353         const double K = norm(RayDirection);
354         const Ray3d ray(2.0 * SphereCenter - RayOrigin, -RayDirection / K, 1.0 * K, 6.0 * K);
355 
356         double t_out[2] = { FP<double>::snan(), FP<double>::snan() };
357         const size_t hits = intersect_sphere(ray, SphereCenter, SphereRadius, t_out);
358 
359         ASSERT_EQ(2, hits);
360         EXPECT_FEQ(1.4 * K, t_out[0]);
361         EXPECT_FEQ(3.0 * K, t_out[1]);
362     }
363 }
364