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