The Ray Tracer Challenge in Kotlin
January 10, 2021
During the chrismas holiday I started a new ray tracer in Kotlin while following the book “The Ray Tracer Challenge.
In this book the ray tracer is developed step by step from first principles using the methodology of Behavior-Driven Development (BDD).
All the requirements are formulated in the specification language of cucumber. The following example specifies the intersection of a sphere.
Scenario: An intersection encapsulates t and object
Given s ← sphere
When i ← intersection(3.5, s)
Then i.t = 3.5
And i.object = s
These scenarios are implemented in JUnit5 unit tests in Kotlin using annotations provided by the cucumber framework to match the specification to the unit test.
class IntersectionsStepDefinitions {
@When("i ← intersection\\({double}, s)")
fun i_intersection_s(double1: Double?) {
i = Intersection(double1!!, s)
}
@Then("i.t = {double}")
fun i_t(double1: Double?) {
assertEquals(double1!!, i!!.t)
}
So every phrase of the BDD specification is translated to a code fragment for the unit test. This leads to the implementation of a domain specific language (DSL) that describes the problem domain.
The implementation is left to the reader. I could reuse some code from my “from the ground up” ray tracer.
class Sphere : Shape() {
override fun intersectInObjectSpace(ray: Ray): Intersections {
val toRayOrigin = ray.origin.toVector()
val a = ray.direction dot ray.direction
val b = 2 * (ray.direction dot toRayOrigin)
val c = (toRayOrigin dot toRayOrigin) - 1
val discriminant = b * b - 4 * a * c
if (discriminant < 0.0) {
return Intersections()
}
val t1 = (-b - sqrt(discriminant)) / (2 * a)
val t2 = (-b + sqrt(discriminant)) / (2 * a)
return Intersections(isec(t1), isec(t2))
}
Until now I implemented chapters 1 to 10. The code is in a git repository.