I had a hunch that this day will come. The day when I must face my design decisions so far and to deal with them to be able to do the challenges for the rest of the book. So it’s not a supprise that at this point the goal of the chapter is to refactort the code written so far.
And…there was a very long pause since my last post. To keep story short there was first a bug in the inverse function in the Apples simd framework. Since I didn’t implement the inverse function my self (like the book told me to do) I was kind of a stuck. And yes I know I might just as well had been able to implement that functionality in that point, but I was a bit lazy so I didn’t. And then I got my new MacBook Pro 16″ and had some problems whit that. The logic board, display and touch-ID module are all replaced and now it works like charm. Few moths went pretty quickly and when the Xcode was updated and the macOS was updated and the bug was gone I realised that after such a long pause it was just pain to get going with “old” project. I had forgotten all the routines and the code written so far looked a little weird in some places 🙂 In some way I finally managed to put all the pieces together again.
Refactoring Shapes
Jamis refer to classes, objects, parents and inheritance. I start this refactoring proces by using a protocol. Basically it’s a same thing in Swift as interfaces are in Java. So I created a protocol called RGShape and put all the things that are common to all shapes in that protocol and left the implementation details in the structs. I could have used classes and inheritance, and it even might have been a better and cleaner way to do this kind of refactoring (I’m not sure yet), but I wanted to give a try for protocols.
The goal of this next step is to take your Sphere implementation, identify the functionality that will be common to all shapes, and refactor those bits into an abstract parent that all other shapes will inherit from.
Jamis Buck
In the book there is a list with four items that we need to deal at first. So here is my RGShape protocol and the four items are explained in the code.
Since I decided to use a protocol here I will not create any separate shape struct. I just need to make sure my current struct which is RGSphere, implements the RGShape protocol and I will run my test against that.
In Swift we can use structs to implement protocols but structs can’t use inheritance. So this fact, that I’m using structs now, kind of forces me to use protocols instead of the cleaner inheritance pattern, that is only possible with classes. One option might have been to change the current structs to classes and use inheritance. It might not even been that much of a work to do, but as previously mentioned I wanted to try protocols.
A bigger change would be to change my interserctRGSphere function to work with RGShape protocol type of objects. Chage will include changes in RGIntersection struct because at the moment it takes only RGSpheres as input and also there is the normalAt function that needs to changed….and RGComputation….and… . Well maybe I take this one bite at a time.
Intersect
I renamed the intersectRGSphere function to intersect since it’s purpose is now to handle all the intersections to what ever the type of the shape is. Previously it took RGSphere as input but now it takes RGShape aka. the protocol called RGShape or to be precise an object that implements the RGShape protocol. So the logic that was previously in there for RGShpere is now moved into to the RGSpheres localIntersect function and the intersect function is more generic. The RGSphere looks like this:
Like the book tells us the intersect function handles the transforming the ray into the local space and the localIntersect then calculates the intersection. This way the responsibilities are divided so that the localIntersect takes care of the job done in local space and the intersect function takes care of transforming the ray and calling the proper localIntersect function.
Sorry if I repeat my self but I guess I need to repeat this thing for my own sake so that I get it right. I think this is, in general, a brilliant solution. There is nothing novel in it I know. The solution is very common in OOP style and I try to mimic it here using the protocol. But I haven’t done conding that much at all so every time I found a real world solution where these concepts are actually used to make a difference, I found it very rewarding 🙂
Testing
In the testing perspective we are interested only if the transformations and the defaults for a shape (just any shape) are correct. To test the the rays transformation I implemented the first of the two possible methods mentioned in the book which is to create a global variable for the ray. When the ray is given to the intersect function it gets transformed. After that it is send to the localIntersect function. We need to have that transformed ray somewhere so we can check in our tests that the transformations actually happened correctly. I put one test case here and explain the flow in code. I wont’t put all tests here. Those can be found in the projects source repo. So this is the test case:
Local normal
The goal is starting to form here. The generic idea is to convert things to local space, and then call the specific method for the job at hand to handle the task in local space and then move the result back to world space. Like with the intersections.
Next we need to deal with the normals and the logic is quite the same. Every real object will have it’s own localNormalAt function which is called at higher level at the stack. The global function that calls the localNormal is self explanatory.
The steps here are the ones mentioned above.
- Move the point of interest into shapes local space
- Calculate the shapes normal in local space
- Move the normal back in world space
- Retur normal
Implementing a Plane
So far everything had been spheres. Modified ones but spheres anyway. No it’s time to change that so we add a plane in the game. Visually a plane is cool thing. In my opinion. It’s simple and yet it can be very expressive. But quoting to Jamis, in the point of view of coding it is a bit boring.
Every single point on the plane has the same normal: vector(0, 1, 0). This means that implementing the local_normal_at() function for the plane is rather uninteresting!
Jamis Buck
In my implementation I created a RGPlane struct. It implements the RGShape protocol because a plane is a one form of a shape and have those common shape characteristics.
That was all there is at the moment. There is lots of small naming changes in the code but the big lines are here.
Putting it together
I’m not going to go trough the playground for this chapters putting it together. That is easily discovered from the source code. But what I want to point out is that when problems arise it could be sometimes confusing and hard to find the source of those problems.
After getting all the test for this chapter succeed I was sure that the putting it together part would be easy. Just put it together 🙂 But after my first run the result was like in the image below. Not as nice as the picture in the book. What was wrong? The tests was ok!
I get some help from Jamis and he pointed me a post on the forum that might help https://forum.raytracerchallenge.com/thread/57/shadows-point-epsilon-00001-causes?page=2 . First the case looked promising but after studying it better it was not the same problem as mine. In my case the shadows was wrong and there was this “akne”.

At the moment I’m not able to recreate the state where the code was when the error happened. I was happy to get the code working so I kind of missed to write down the cause. But two things was influencing the bug. First there was an extra transformation in my code and then there was the time when invert function wasn’t working because of the bug in simd library. Refactoring code should be done in one flow. If there is time gaps in the coding that are as big as mine was the odds are great that something goes easily wrong.
Eventually I got the putting it together right and the image below is the final result. So far I have learned a lot and the most valuable thing is probably not to give up. An other thing is that I remember my grandmother used to say: “A little in a day and a lot in a week”. (Actually an old Finnish wisdom I guess). Fits well in here. Meaning that do little every day and don’t keep too long pauses for it is hard to get going again once you have lost your touch 🙂
