The Ray Tracer Challenge: Capped cone normal vector (page 190)

Hi @jamis,
I’m not sure if this has been discussed elsewhere, so apologies for a potential duplicate.

I refer to cone normal calculation at page 190, where author states "

Lastly, for the normal vector, compute the end cap normals just as you did for the cylinder, but …"

This unfortunately doesn’t work since the code for cylinder cap normal assumes constant diameter of 1.0. Instead one should calculate an actual distance from y axis (including sqrt) and check it against the currently considered y limit’s absolute value.

Pseudo codes and tests

Assumed original pseudo-code (combining cylinder and cone):

function local_normal_at(cone, point)
    #part from the cylinder caps check
    dist = point.x ^ 2 + point.z ^ 2
    # if squared distance is < 1.0 then distance is also < 1.0 therefore we don't need sqrt here
    if dist < 1 and point.y >= cone.maximum - EPSILON
         return vector(0, 1, 0)
    else if dist < 1 and point.y <= cone.minimum + EPSILON
       return vector(0, -1, 0)
    else
        y = sqrt(point.x ^ 2 + point.z ^ 2)
       if point.y > 0.0
           y = -y
       return (point.x, y, point.z)

Test that fails with above example:

Scenario Outline: Computing the normal vector on a capped cone
    Given shape = cone()
    And shape.minimum = -1
    And shape.maximum = 2
    And p  = point(1.5, 2, 0.0)
    When n = local_normal_at(shape, p)
    Then n = point(0, 1, 0)

This test assumes a point that should be located at the upper cone cap with distance from y axis larger than 1.0. In this case we expect the cap’s (plane) normal to be returned.\

With the above pseudocode, we get instead vector(1.5, -1.5, 0)

Pseudocode of a proposed fix:

function local_normal_at(cone, point)
    #calculate distance from y axis,
    dist = sqrt(point.x ^ 2 + point.z ^ 2)
    if dist < cone.maximum and point.y >= cone.maximum - EPSILON
         return vector(0, 1, 0)
    else if dist < cone.minimum and point.y <= cone.minimum + EPSILON
       return vector(0, -1, 0)
    else
        # note that y calculation is same as dist, we can potentially reuse the value here
        y = sqrt(point.x ^ 2 + point.z ^ 2)
       if point.y > 0.0
           y = -y
       return (point.x, y, point.z)