Meta-Programming Raytracer (1/3)

Yes, I was crazy enough to try to develop a compile-time raytracer only by using C++ meta programming. I used extensively the Boost::MPL library, but I had to develop a lot of new classes to be able to handle floating-point types, and to be able to perform standard algorithms on them, like sqrt, but also to manage 3D vectors, sphere-ray intersections, 2D images, etc.

My idea in this post and the following is to describe what I had to face during the project and how I overcome the problems, not to describe each class in detail, which would not be very interesting and understandable. Indeed, as I realized doing this project, finished meta-programming code tend to be very complex to read.

Main idea and challenges

With meta programming, you’re really leaving the regular C++ world, and entering the pure functional world. If you’ve never been developing in a pure functional language, it can gives you serious headaches. My experience in this field was quite limited, so it was interesting to try to do something that I know well (a raytracer) but using something I’m not familiar with (functional language). The other idea behind this project was to improve my understanding of boost::mpl, and you’re always learning better when doing a full project than simple examples.

The other challenge with meta programming in C++ is that error messages tend to be very very unreadable. The smallest mistake can lead to hundreds of obscure error messages. My advice to deal with that problem is therefore to do one thing at a time, not build entire classes but small pieces that you mix later on and make sure it works well.

For a raytracer, you really need to handle floating point values. Implementing a raytracer with only integer is surely possible but not that easy, so my first task was to implement some classes to handle floats. I therefore developed a few classes to do that.

Basic classes

Rational class

The first class is the rational one. It’s storing a floating point value as Numerator/Denominator, with both values stored as integers. The rational class is used like this:

typedef mpb::rational<7,3> ::type f1;

which will hold the value 7/3. I then implemented standard operations like +,-,* on rationals, using mpl-compatible operators (mpl::plus, mpl::times, etc.). After a couple of tests, I found that the stored values of the numerator and denominators tended to grow very fast, and were overflowing the limit of integers. I fixed that using two solutions, described below.

New basic type: mpl::llong

I used long long values instead of integers for the numerator and denominator. To do this, I developed the mpb::llong class, based on the existing mpb::long one.

Automatic reduction of rationals

Instead of storing the two supplied values for numerator and denominator, I modified the existing rational class to store a reducted rational. This is done using the standard Euclidian algorithm, which is recursive and is therefore straightforward to implement.

float_ class

Supplying values as rationals is effective, but not very user-friendly. Entering 3/2 instead of 1.5 is not something a programmer is accustomed to, so I developed that other class to be able to do that. In the end, my float_ class lets you type:


typedef mpb::float_<0,5> ::type f1;
typedef mpb::float_<7,3> ::type f2;

which will store the 0.5f and 7.3f values. The float class is based on rational, so with the automatic reduction described below, these two values will actually be stored as 1/2 and 73/10.

More on next post…

Leave a Comment

Please note: Comment moderation is enabled and may delay your comment. There is no need to resubmit your comment.