Raytracer challenge in Golang: up to Chapter 7

Progress has been made, and I have a scene

I've just completed Chapter 7. Chapter 5 created a flat sphere. Chapter 6 was the first time a sphere had depth. The first full-fledged scene is rendered in Chapter 7. Three spheres of various sizes, with walls and floors.

More learnings from these sections.

Gherkin test match expressions.

Using Godog, you build your test matcher using regular expressions. Godog looks at each Gherkin command, regexp matches it against all the commands you've registered, and if it finds a match, it runs that command. Godog is nice enough that every captured () statement is sent as a parameter to the function it calls. So.

  1. Liberal use of (?:) to NOT store a match when you're just matching to check
  2. Godog converts to string and int fine. Floats get... finicky. For now, I've made a StringToFloat64 function so that I can have control over how strings are floated. Catches x/y fractions, things like Pi and SquareRoot.
  3. Simple matches are better than complex matches. For the StringToFloat64, I ended up with (-?\d+(?:([\./])\d)?) and various silliness before I just resorted to (.+) and having anchors nearby like [,)]?

Slice maps and pointers

When I was building my test scripts, I needed a way to store created variables to look up between steps. I used the tupletest object and simply tagged in map[string]type slices to keep the variables in with names to find them. They were all stored as basic objects - which apparently means if I try to access them via (x *object) pointery methods, it fails as you can't reference a map's pointer reference.

To get around this, I had to create a temporary copy of the map[index]... thing, do the object manipulations, then store it back in the map. I forgot this a few times, but thankfully testing found that quickly.

Golang kinda-sorta-notreally objects

Without object inheritance, all the docs I read said to use interfaces. With an interface you define what functions any inheritor has to provide; and then you can use the interface as a stand-in parameter that means "anything of this interface can go here."

 2type Shaper interface {
 3	Equals(t Shaper) bool
 4	Intersects(r Ray) map[int]Intersection
 5	GetID() int
 6	SetOrigin(t Tuple)
 7	GetOrigin() Tuple
 8	SetTransform(t Matrix)
 9	GetTransform() Matrix
10	NormalAt(t Tuple) Tuple
12	GetMaterial() Material
13	SetMaterial(m Material)

That's my current "Omni" object for shapes. My Sphere struct must implement all of those functions to be considered a Shaper; which means anything I pass into a function that takes a Shaper I know I can use any of those functions. It's handy, but a bit tricky. I'm guessing that for Parent inheritance I can create generic functions that take a Shaper argument as a pointer? Maybe?

Code is currently up on Github if you want to play along or offer me advice.


Got two more chapters done, I have shadows AND a new plane object..

No inheritance

Mo problems.

Not being able to have a parent:: style ability to have common functions between objects is a right pain. I'm getting around it at the moment by having a generic function (e.g. for NormalAt) that's called and you pass in the object you want to run the Local for - so it can do the surrounding bits. It's kludgey, but I can't think of an alternative.