Finding the Normal on a Child Object

Remember back in Transforming Normals, when you used the shape’s transformation matrix to manipulate the normal vector? The same thing needs to happen when computing the normal on a child object of a group, but now there’s a complication: when an intersection is found with a group, the intersection record itself references the intersected child. As your ray tracer is currently implemented, this means that when you compute the normal vector on that child object, only the child’s transforms are considered, and not the transforms of any group the child may belong to.

This is what you’ll work on next, in three steps:

  • Write a function that converts a point from world space to object space, recursively taking into consideration any parent object(s) between the two spaces.

  • Write a function that converts a normal vector from object space to world space, again recursively taking into consideration any parent object(s) between the two spaces.

  • Update the normal_at function so that it calls these two new functions to transform the incoming point and outgoing vector appropriately.

Got it? Here goes!

Test #7: Convert a Point from World Space to Object Space

Take a point in world space and transform it to object space, taking into consideration any parent objects between the two spaces.

This test constructs an outer group, which contains an inner group, which in turn contains a sphere. Each is given its own transformation before calling a new function, world_to_object(shape, point), to convert a world-space point to object space.

 Scenario​: Converting a point from world to object space
 Given​ g1 ← group()
 And​ set_transform(g1, rotation_y(π/2))
 And​ g2 ← group()
 And​ set_transform(g2, scaling(2, 2, 2))
 And​ add_child(g1, g2)
 And​ s ← sphere()
 And​ set_transform(s, translation(5, 0, 0))
 And​ add_child(g2, s)
 When​ p ← world_to_object(s, point(-2, 0, -10))
 Then​ p = point(0, 0, -1)

Make this test pass by implementing world_to_object(shape, point). If shape has a parent, the function should first convert the point to its parent’s space, by calling world_to_object(parent, point). The result is then multiplied by the inverse of the shape’s transform. In pseudocode, it looks like this:

 function​ world_to_object(shape, point)
 if​ shape has parent
  point ← world_to_object(shape.parent, point)
 end​ ​if
 
 return​ inverse(shape.transform) * point
 end​ ​function

Next up, you’ll convert a vector from object to world space.

Test #8: Convert a Normal Vector from Object Space to World Space

Take a normal vector in object space and transform it to world space, taking into consideration any parent objects between the two spaces.

This sets up two nested groups like in the previous test. Again, each is given its own transformation, and then another new function, normal_to_world(shape, normal), is used to transform a vector to world space.

 Scenario​: Converting a normal from object to world space
 Given​ g1 ← group()
 And​ set_transform(g1, rotation_y(π/2))
 And​ g2 ← group()
 And​ set_transform(g2, scaling(1, 2, 3))
 And​ add_child(g1, g2)
 And​ s ← sphere()
 And​ set_transform(s, translation(5, 0, 0))
 And​ add_child(g2, s)
 When​ n ← normal_to_world(s, vector(√3/3, √3/3, √3/3))
 Then​ n = vector(0.2857, 0.4286, -0.8571)

You can make this test pass by first converting the given normal to the parent object space using the algorithm you implemented in Transforming Normals. Take the inverse of the shape’s transform, transpose the result, and multiply it by the vector. Normalize the result. Then, if the shape has a parent, recursively pass the new vector to normal_to_world(parent, normal). Here’s the implementation in pseudocode:

 function​ normal_to_world(shape, normal)
  normal ← transpose(inverse(shape.transform)) * normal
  normal.w ← 0
  normal ← normalize(normal)
 
 if​ shape has parent
  normal ← normal_to_world(shape.parent, normal)
 end​ ​if
 
 return​ normal
 end​ ​function

Once those tests are passing, you’re ready to find the normal on a child object.

Test #9: Find the Normal on an Object in a Group

Find the normal on a child object of a group, taking into account transformations on both the child object and the parent(s).

As with the previous two tests, this one sets up a hierarchy of two groups and a sphere and assigns them each a transformation. It then find the normal vector at a point on the sphere (in world space), using the normal_at function.

 Scenario​: Finding the normal on a child object
 Given​ g1 ← group()
 And​ set_transform(g1, rotation_y(π/2))
 And​ g2 ← group()
 And​ set_transform(g2, scaling(1, 2, 3))
 And​ add_child(g1, g2)
 And​ s ← sphere()
 And​ set_transform(s, translation(5, 0, 0))
 And​ add_child(g2, s)
 When​ n ← normal_at(s, point(1.7321, 1.1547, -5.5774))
 Then​ n = vector(0.2857, 0.4286, -0.8571)

Next, update your normal_at function to use your new world_to_object and normal_to_world functions, calling the former to convert the world-space point to object space before calculating the normal, and then calling the latter to convert the normal back to world space. In pseudocode, your updated normal_at function should come together like this:

 function​ normal_at(shape, world_point)
  local_point ← world_to_object(shape, world_point)
  local_normal ← local_normal_at(shape, local_point)
 return​ normal_to_world(shape, local_normal)
 end​ ​function

You’re just about done with groups, but there’s one more bit to address. In Transforming Patterns, you allowed patterns to be transformed by converting points from world space to object space, and from there to pattern space, before computing the color. For those patterns to behave nicely when applied to objects in groups, you’ll need to use this new world_to_object function when converting points from world space to object space. Otherwise, the patterns won’t apply the group transformations and won’t look like you expect. You’re on your own for this one; make it so!

Joe asks:
Joe asks:
Where is the group’s local_normal_at function?

Ah, you noticed it was missing! Well done, but it’s not a mistake or oversight. Because normals are always computed by calling the concrete shape’s local_normal_at function, the group itself doesn’t need one. In fact, if your code ever tries to call local_normal_at on a group, that means there’s a bug somewhere.

Consider implementing local_normal_at for groups, but having the implementation throw an exception or otherwise cause an error. This can help you catch those bugs earlier and makes it explicit that groups are abstract and don’t have normal vectors.

That should about do it for your implementation of groups. You’ll work through an exercise shortly to get familiar with how to use them, but first, let’s take a quick look at how these groups can be used to optimize your ray tracer.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.223.21.5