Game Diary: Vector Space

I'm absolutely loving using Swift as a language for Apple's platforms. It has a lot of the things I came to love about Objective-C (not all of them, I think the message syntax is far superior to the function syntax that Swift has), and a lot of other things that I just love, full stop.

While wiser developers are avoiding features like operator overloading, and extensions, I'm going all in on them to see what I can make my client code look like. I'm a big fan of making things easy to read above all.

So note that if you see code examples on this blog and they don't work, this is probably why. Consider my code more like pseudocode, and you'll go a lot further.

I've been collecting these extensions together, with the intention of having some kind of library or boot strap for building Sprite Kit games in Swift. I might put it on Github at some point before that, but for now I'd just like to write a little about some of the things I have inside, and why. I'm going to start with vector spaces.

Core Graphics has many struct types that are almost identical. CGs Vector, Point and Size. We can abstract a few of those, and have some fun with Swift at the same time.


protocol VectorSpace {
    var identity: Self { get }
    var magnitude: CGFloat { get }
    
    func multiplyByScalar(scalar: CGFloat) -> Self
    func add(right: Self) -> Self
}

You can see we say we need two operations, namely addition and scalar multiplication. I don't allow the choice of a field (although with generics perhaps we could), I instead say you have to use CGFloat.

With every type I have added VectorSpace conformance to, they have been representing traditional \( \mathbb{R}^2 \) vectors, and so these axioms are moot. With component-wise addition and multiplication, all of them hold. Kind of, when you think about floating point errors.

The thing is though, once you have these axioms, a lot of other things become true, and a lot of syntax becomes nicer. For example, we can implement scalar multiplication as an infix operator:


func *(left: CGFloat, right: V) -> V {
    return right.multiplyByScalar(left)
}

And just like that, we can multiply vectors by numbers. I also define them for the other way around, and division, and addition, and unary subtraction. Everything you might need.

How might we add conformance to a type? Well, let's look at how I did it for CGPoint. My implementation on all the others is almost line for line identical.


extension CGPoint: VectorSpace {
    var identity: CGPoint {
        return CGPointZero
    }
    
    var magnitude: CGFloat {
        get {
            return sqrt(
            	self.x * self.x +
                self.y * self.y)
        }
        
        set(newMagnitude) {
            self *= (newMagnitude /
            	self.magnitude)
        }
    }
    
    func multiplyByScalar(scalar: CGFloat) -> CGPoint {
        return CGPointMake(
            self.x * scalar,
            self.y * scalar)
    }
    
    func add(right: CGPoint) -> CGPoint {
        return CGPointMake(
            self.x + right.x,
            self.y + right.y)
    }
}

I also made magnitude a writable parameter as well, which comes in handy. But the main point is the two methods: there really isn't a lot to them. We can delete a lot of code now, though. Before we do that, I want to introduce one new extension that will come in very useful:


extension CGPoint {
    func relativeTo(newOrigin: CGPoint)
            -> CGPoint {
        return self - newOrigin
    }
}

The code that I would need to do this otherwise, is


let relative = CGPointMake(
    old.x - origin.x,
    old.y - origin.y
)

Compared to


let relative = old.relativeTo(origin)

The difference isn't that much in lines of code (although those do add up), but I find it a lot easier to see what's happening in the second example. I'm a mathematician, and I still have to think and draw a picture to do vectors relative to new origins. Having this means it can be inlined into a function call, as well, saving us from the pain of having to come up with a name that describes the result of the byzantine mathematical operation we just did.