Let’s wrap this up with an example of how you can use groups in your scenes. You are going to build a model of a hexagon using cylinders and spheres, like this:
You’ll build this by first defining a single instance of each component: one sphere (to become the corners of the hexagon), and one cylinder (to become the edges). You’ll transform each into place once, and add them to a group. Then, you’ll create duplicates of that group, rotating each duplicate around the y axis until the whole hexagon is constructed.
Start by writing a function that creates the prototypical sphere component, scaling it by 25% and translating it -1 unit in z. The following pseudocode shows this as a function named hexagon_corner.
| function hexagon_corner() |
| corner ← sphere() |
| set_transform(corner, translation(0, 0, -1) * |
| scaling(0.25, 0.25, 0.25)) |
| return corner |
| end function |
Remember that when you combine matrix transformations, you do so in reverse order. Thus, though the pseudocode for hexagon_corner multiplies the translation by the scaling, the result is that the sphere is scaled first and then translated. |
Next, write a function that creates the prototypical cylinder component. Limit it to a minimum of y=0 and a maximum of y=1, and scale it by 25% in x and z. Rotate it -π/2 radians in z (to tip it over) and -π/6 radians in y (to orient it as an edge). Then, translate it -1 unit in z. In pseudocode, this hexagon_edge function might look like this:
| function hexagon_edge() |
| edge ← cylinder() |
| edge.minimum ← 0 |
| edge.maximum ← 1 |
| set_transform(edge, translation(0, 0, -1) * |
| rotation_y(-π/6) * |
| rotation_z(-π/2) * |
| scaling(0.25, 1, 0.25)) |
| return edge |
| end function |
The next step is to join those two primitives into a group, forming one side of the hexagon. The following hexagon_side function demonstrates this in pseudocode.
| function hexagon_side() |
| side ← group() |
| |
| add_child(side, hexagon_corner()) |
| add_child(side, hexagon_edge()) |
| |
| return side |
| end function |
Once you’ve got a function that can return a single side of the hexagon, you can write the final function, hexagon, which calls hexagon_side six times and rotates each piece into place, like so:
| function hexagon() |
| hex ← group() |
| |
| for n ← 0 to 5 |
| side ← hexagon_side() |
| set_transform(side, rotation_y(n*π/3)) |
| add_child(hex, side) |
| end for |
| |
| return hex |
| end function |
From there, you can add a light source and a camera, and go nuts with it!
What other composite shapes can you build? Try creating a stick figure or an automobile. Trees and plants are definitely possible, too, and lend themselves well to fractal algorithms like Lindenmayer systems.
Also, you may soon realize that materials applied to a group have no effect at all on the shapes it contains. What if you wanted the shapes in your ray tracer to be able to “inherit” materials from their parents? How might you extend your code to make that happen?
Give it some thought. Then, once you’ve played with this new feature enough, read on. You’ll add your final primitive in the next chapter: the triangle.
3.19.211.134