Plan ahead

This is a big assignment. Start NOW, or you will probably not finish.

I recommend laying out a plan of attack before coding. To assist in your planning, here is an outline of the steps your ray tracing program will need to do:

  1. Read file format into scene data and list of objects
  2. For each pixel:
    1. Calculate pixel location in world space
    2. Calculate ray from eye point through the pixel and into the scene
    3. Calculate ray-object intersections, choose smallest/closest.
    4. Set the pixel to that color
  3. Write all pixels out to your PPM file

It's a reasonable strategy to attack these one at a time: first build your parser and confirm you can read the file correctly (1). Then create an image of the correct size and write it out (3). Then compute the pixel locations (2.1), then ray directions (2.2), then intersections (2.3)

Using C++

The assn0 sample code included a linear algebra vector class. You don't have to use this, but if you don't I'd encourage you to make your own. Creating a C++ 3D linear algebra vector class will make many operations more compact and more like the math equivalent. I'd recommend at least addition and scalar multiplication operators, along with functions for vector length, normalization, dot product and cross product.

The std::list and/or std::vector data structures may also be useful for this assignment for your list of objects. I do not recommend using either of these for your 3D vectors, a simple struct or array for their data will have less overhead since they do not need to be dynamically resized, and you'll be using them enough that the added overhead will impact your run time.

The std::map data structure may be useful to map "surface" names to the properties for that surface

To handle more than one primitive type, a common effective strategy is to create a generic object class with spheres and polygon classes derived from it, each with a specialized virtual method to compute the intersection of a ray with that primitive type.

Parsing

For a file format as simple as this one, it isn't necessary to get too fancy with the parsing. The easiest method is probably to read one whitespace-delimited string token at a time, then compare that to each of the keywords you handle. Once you have found a keyword, read in the expected number of numbers or strings. Knowing we'll only use valid files, you can simplify your parser significantly bu just skipping unknown tokens until you find one you recognize.

Make a class or struct to hold the data that you parse (background color, eye position, list of objects, etc.). You can do your actual parsing in the constructor or a member function of that class.

Test input

It is very difficult to find the error in one of hundreds or thousands of primitives without a systematic approach and simplified data.

Note that you can easily write your own input files by hand, which can be very handy for debugging. I recommend that you start with a test scene looking from (0,0,0) at (0,0,2) with (0,1,0) up. Create just a single a single sphere in the file, centered at (0,0,2) with a radius of 1. With a single sphere, it's easier to tell if your loading is working, and with a simple view it is easier to tell if your ray positions are correct.

Start by trying to find intersections with a 1x1 pixel image, which should give you a ray straight down the Z axis with direction vector (0,0,2). You should get two hits along that ray: a closest hit at (0,0,1) with t=0.5 and second hit at (0,0,3) with t=1.5. Move the sphere around, making sure you get the right answers when you miss it, when you are inside the sphere, or when it is behind you. Then, you can scale up to 2x2 or 3x3 images to make sure your ray direction code is correct for other pixels in the image. Once you have the basics working, move up to a larger window and wider field of view so you can visually tell if your sphere is rendering as a sphere.

Similarly, try a simple test file with a polygon spanning (0,1,0), (-1,-1,0) and (1,-1,0). With those vertices, a ray through (0,0,0) should have barycentric alpha, beta, and gamma of 0.5, 0.25, and 0.25 (possibly in a different order depending on vertex ordering)

Only when you are confident of your ray direction computation and intersection code should you switch to the SPD scenes. If you still run into problems, you can successively eliminate spheres from the SPD file to find one that is not being computed correctly

Debugging output

It is also worthwhile getting image output working early. Printing debugging values works OK for one pixel/one primitive scenes, but for larger images and scenes, outputting values other than colors at each pixel can be a valuable debugging tool. For example, it can be very handy to visualize ray intersection distance as color. Just scale the distance so the values you expect will map into the 0 to 1 color range so they will be visible. When mapping floating point values to 8-bit color, be sure to clamp values that are out of range to avoid having them wrap around when cast to byte.