Either the ray misses the cylinder or it hits the cylinder. Right? This dichotomy neatly describes the tests you’ll write first. You’ll start by confirming that a ray misses a cylinder. Such tests can be made to pass trivially, but rather than passing them by making your local_intersect method do nothing, this provides a good opportunity to start actually implementing the intersection routines.
Show that the local_intersect function correctly identifies when a ray misses a cylinder.
This test creates a cylinder and casts three different rays at it. The first ray is positioned on the surface and points along the +y axis, parallel to the cylinder. The second is inside the cylinder and also points along the +y axis. The third ray is positioned outside the cylinder and oriented askew from all axes. All three should miss the cylinder.
| Scenario Outline: A ray misses a cylinder |
| Given cyl ← cylinder() |
| And direction ← normalize(<direction>) |
| And r ← ray(<origin>, direction) |
| When xs ← local_intersect(cyl, r) |
| Then xs.count = 0 |
| Examples: |
| | origin | direction | |
| | point(1, 0, 0) | vector(0, 1, 0) | |
| | point(0, 0, 0) | vector(0, 1, 0) | |
| | point(0, 0, -5) | vector(1, 1, 1) | |
The algorithm that implements this shares some features with the ray-sphere intersection algorithm. As with the sphere algorithm, you’ll compute a discriminant value, which will be negative if the ray does not intersect. Here’s some pseudocode:
| function local_intersect(cylinder, ray) |
| a ← ray.direction.x² + ray.direction.z² |
| |
| # ray is parallel to the y axis |
| return () if a is approximately zero |
| |
| b ← 2 * ray.origin.x * ray.direction.x + |
| 2 * ray.origin.z * ray.direction.z |
| c ← ray.origin.x² + ray.origin.z² - 1 |
| |
| disc ← b² - 4 * a * c |
| |
| # ray does not intersect the cylinder |
| return () if disc < 0 |
| |
| # this is just a placeholder, to ensure the tests |
| # pass that expect the ray to miss. |
| return ( intersection(1, cylinder) ) |
| end function |
Note that the last line of the function, returning a single intersection at t=1, ensures that the tests pass because the ray truly misses the cylinder and not simply because the function wasn’t doing anything else. You’ll flesh that bit out next, in test #2.
Show that the local_intersect function correctly identifies when a ray hits a cylinder.
Once again, the scenario outline creates three different rays, each of which is expected to intersect the cylinder. The first is configured to strike the cylinder on a tangent, but even though the actual intersection is at a single point, you’ll still make your code return two intersections, both at t=5. (This mimics how you handled tangent intersections in Chapter 5, Ray-Sphere Intersections, and will help with determining object overlaps in Chapter 16, Constructive Solid Geometry (CSG).) The second ray intersects the cylinder perpendicularly through the middle and results in two intersections at 4 and 6. The last ray is skewed so that it strikes the cylinder at an angle.
| Scenario Outline: A ray strikes a cylinder |
| Given cyl ← cylinder() |
| And direction ← normalize(<direction>) |
| And r ← ray(<origin>, direction) |
| When xs ← local_intersect(cyl, r) |
| Then xs.count = 2 |
| And xs[0].t = <t0> |
| And xs[1].t = <t1> |
| |
| Examples: |
| | origin | direction | t0 | t1 | |
| | point(1, 0, -5) | vector(0, 0, 1) | 5 | 5 | |
| | point(0, 0, -5) | vector(0, 0, 1) | 4 | 6 | |
| | point(0.5, 0, -5) | vector(0.1, 1, 1) | 6.80798 | 7.08872 | |
Make this pass by using the discriminant to find the t values for the points of intersection. The highlighted lines in the following pseudocode demonstrate the calculation you need:
| function local_intersect(cylinder, ray) |
| a ← ray.direction.x² + ray.direction.z² |
| |
| # ray is parallel to the y axis |
| return () if a is approximately zero |
| |
| b ← 2 * ray.origin.x * ray.direction.x + |
| 2 * ray.origin.z * ray.direction.z |
| c ← ray.origin.x² + ray.origin.z² - 1 |
| |
| disc ← b² - 4 * a * c |
| |
| # ray does not intersect the cylinder |
| return () if disc < 0 |
| |
» | t0 ← (-b - √(disc)) / (2 * a) |
» | t1 ← (-b + √(disc)) / (2 * a) |
» | |
» | return ( intersection(t0, cylinder), intersection(t1, cylinder) ) |
| end function |
All that’s left before you can actually render this cylinder is to compute the normal vector.
18.224.0.25