' '
def material(c)
{
  Materials.uniform( [ "ambient": Colors.white() * 0.1,
                       "diffuse": c * 0.8,
                       "specular": Colors.white() * 0.5,
                       "specular_exponent": 10,
                       "reflectivity": 0.0,
                       "transparency": 0,
                       "refractive_index": 0 ] )
}

def scene_at(now)
{
  var eye_position = Animations.lissajous( [ "x_amplitude": 5,
                                             "x_frequency": 1,
                                             "x_phase": degrees(90),
                                             "y_amplitude": 5,
                                             "y_frequency": 1,
                                             "y_phase": degrees(0),
                                             "z_amplitude": 5,
                                             "z_frequency": 1,
                                             "z_phase": degrees(0),
                                             "duration": seconds(5) ] )


  var camera = Cameras.perspective( [ "eye": eye_position[now],
                                      "look_at": pos(0,0,0) ] )


  var p1 = Pos.spherical(1, degrees(0), degrees(90))
  var p2 = Pos.spherical(1, degrees(60), degrees(-30))
  var p3 = Pos.spherical(1, degrees(180), degrees(-30))
  var p4 = Pos.spherical(1, degrees(-60), degrees(-30))

  var root = union( [ decorate( material(Colors.red()), triangle(p1, p2, p3) ),
                      decorate( material(Colors.green()), triangle(p1, p3, p4) ),
                      decorate( material(Colors.blue()), triangle(p1, p4, p2) ),
                      decorate( material(Colors.yellow()), triangle(p2, p4, p3) ) ] )

  var lights = [ Lights.omnidirectional( pos(0, 5, 0), Colors.white() ), Lights.omnidirectional( pos(0, -5, 0), Colors.white() ) ]

  create_scene(camera, root, lights)
}

var raytracer   = Raytracers.v6()

var renderer    = Renderers.standard( [ "width": 500,
                                        "height": 500,
                                        "sampler": Samplers.multijittered(2),
                                        "ray_tracer": raytracer ] )

pipeline( scene_animation(scene_at, seconds(5)),
          [ Pipeline.animation(30),
            Pipeline.renderer(renderer),
            Pipeline.studio() ] )

1. Implementation

1.1. Setting Things Up

Create new files primitives/triangle-primitive.h and primitives/triangle-primitive.cpp. Introduce a factory function

Primitive triangle(const math::Point3D& vertex1,
                   const math::Point3D& vertex2,
                   const math::Point3D& vertex3);

Since you have no TriangleImplementation class yet, you can’t implement this factory function yet.

1.2. TriangleImplementation

In primitives/triangle-primitive.cpp, create a new class TriangleImplementation in an anonymous namespace. Look at primitives/sphere-primitive.cpp for inspiration.

class TriangleImplementation(PrimitiveImplementation):
    def __init__(self, a, b, c):
        self.__a = a
        self.__b = b
        self.__c = c
        self.__n = compute_normal(a, b, c)

Now that we have a class TriangleImplementation with a constructor, we can finish our factory function.

Give the factory function its body.

1.3. find_all_hits

In order to be able to render triangles, we need to implement the ray intersection algorithm.

Implement the method find_all_hits.

Optionally, for better performance (which will make a big difference when used in the context of meshes), implement find_first_positive_hit too.

1.4. bounding_box

The bounding_box method will be crucial when implementing meshes as the bounding box accelerator depends on it. As a reminder: this method should compute a minimally sized math::Box that still fully contains the triangle.

bb

The box must be axis aligned, which means its edges must be horizontal or vertical. Given a triangle, you need to find

  • The x-coordinate of the left side.

  • The x-coordinate of the right side.

  • This forms the box’s x-interval.

  • The same needs to be done for the y- and z-dimension.

Note

Don’t confuse math::Box with the primitive box:

  • The box primitive is a shape (elongated cube) that can be added to a scene.

  • math::Box is merely a mathematical entity, like Rectangle3D. It unambiguously specifies the location and size of the box, but does not contain logic of how to render a box.

Implement the method bounding_box.

1.5. Finishing Touches

Add a binding for the scripting language.

2. Evaluation

Render the scene below:

def material(c)
{
  Materials.uniform( [ "ambient": c ] )
}

def scene_at(now)
{
  var t = Animations.animate(0, 1, seconds(5))[now]

  var camera = Cameras.perspective( [ "eye": pos(0,-5+10*t,5),
                                      "look_at": pos(0,0,0) ] )

  var p1 = Pos.spherical(1, degrees(360*t+0), degrees(90))
  var p2 = Pos.spherical(1, degrees(360*t+60), degrees(-30))
  var p3 = Pos.spherical(1, degrees(360*t+180), degrees(-30))
  var p4 = Pos.spherical(1, degrees(360*t-60), degrees(-30))

  var root = union( [ decorate( material(Colors.red()), triangle(p1, p2, p3) ),
                      decorate( material(Colors.green()), triangle(p1, p3, p4) ),
                      decorate( material(Colors.blue()), triangle(p1, p4, p2) ),
                      decorate( material(Colors.yellow()), triangle(p2, p4, p3) ) ] )

  var lights = [ ]

  create_scene(camera, root, lights)
}

var raytracer   = Raytracers.v1()

var renderer    = Renderers.standard( [ "width": 500,
                                        "height": 500,
                                        "sampler": Samplers.single(),
                                        "ray_tracer": raytracer ] )

pipeline( scene_animation(scene_at, seconds(5)),
          [ Pipeline.animation(30),
            Pipeline.renderer(renderer),
            Pipeline.studio() ] )