Morgemil Part 3 – The Core

I’m going to be taking a bottom-up design approach to this project. Yes, I have an overarching general design, but I fully expect it to change. Change is acceptable and a constant. I’m going to be doing bottom-up design since I am unfamiliar with it, so another chance to learn. I’m not obsessed with keeping my original game design pure as some great mechanics could arise from unforeseen interactions.

Unforeseen interactions is sure to occur since as much of the game as possible will be procedurally generated (another hint, are you keeping track?). Therefore there will be a lot of math involved, not hard math, just a lot of it. So I’m going to start off with some clear functionality that I know will be required. Also, this is a chance to start becoming accustomed to the F# environment.

Here is where I begin. I’m opening Visual Studio 2013 and I’m creating a new solution titled “Morgemil” with one project “Core”.beginningInside this Core project will be a namespace “Math”. The contents of Math will be stuff such as “Vector2”. Yes the core will be re-inventing the wheel but it is a chance for me to sharpen my teeth on F#. So without further ado, Vector2:

module Math.Vector2
 
type Vector2f = 
  struct
    val public X : float
    val public Y : float
    new(x, y) = 
      { X = x
        Y = y }
  end

Let’s go through this. On the first line you see “Math.Vector2”. The first part “Math” implicitly defines a namespace and “Vector2” is the module. By defining “Vector2f” as a struct, this is a value-type and will be useful “primarily for small aggregates of data that are accessed and copied frequently.” Both fields are immutable by default. I’ll see if there is a reason to change that later.

This is certainly not all the functionality needed, so I’m going to go through and do some basic operations. And because I can, I’m going to do some F# Operator Overloading to hopefully make my life easier down the road. This will be so that I can include Vector2 in mathematical notation easily. Not only will Vector2 have to be add and subtract against itself, but scalar types too.

Did you notice that I explicitly named “float” as the member type? Could I make this generic? Sure. But then I would have to deal with making the generic constraints for each mathematical operation (+) and (-) and so on for the type. I’d much rather specialize the struct from the outset and provide some unique behavior. So now we have

/Morgemil/blob/master/Core/Vector2f.fs
module Math.Vector2
 
type Vector2f = 
  struct
    val public X : float
    val public Y : float
 
    new(x, y) = 
      { X = x
        Y = y }
 
    //########## Operator overloads #####################################
    //Addition    
    static member (+) (vec1 : Vector2f, vec2 : Vector2f) = 
      Vector2f(vec1.X + vec2.X, vec1.Y + vec2.Y)
    static member (+) (vec1 : Vector2f, scalar) = Vector2f(vec1.X + scalar, vec1.Y + scalar)
    static member (+) (scalar : float, vec1 : Vector2f) = vec1 + scalar
    //Subtraction
    static member (-) (vec1 : Vector2f, vec2 : Vector2f) = 
      Vector2f(vec1.X - vec2.X, vec1.Y - vec2.Y)
    static member (-) (vec1 : Vector2f, scalar) = Vector2f(vec1.X - scalar, vec1.Y - scalar)
    static member (-) (scalar, vec1 : Vector2f) = Vector2f(scalar - vec1.X, scalar - vec1.Y)
    //Multiplication
    static member (*) (vec1 : Vector2f, vec2 : Vector2f) = 
      Vector2f(vec1.X * vec2.X, vec1.Y * vec2.Y)
    static member (*) (vec1 : Vector2f, scalar) = Vector2f(vec1.X * scalar, vec1.Y * scalar)
    static member (*) (scalar : float, vec1 : Vector2f) = vec1 * scalar
    //Division (Does not guard against divide by zero)
    static member (/) (vec1 : Vector2f, vec2 : Vector2f) = 
      Vector2f(vec1.X / vec2.X, vec1.Y / vec2.Y)
    static member (/) (vec1 : Vector2f, scalar) = Vector2f(vec1.X / scalar, vec1.Y * scalar)
    //########## Member methods #########################################
    //Distance
    member this.LengthSquared() = (this.X * this.X) + (this.Y + this.Y)
    member this.Length() = System.Math.Sqrt(this.LengthSquared())
  end

This is a good start. Is it perfect? No. But it allows some complex statements such as

let one = Vector2f()
let two = Vector2f(5.0, 4.0)
let three = one * two * 5.0 / 3.0 + 5.0 + one

That statement has a meaningless evaluation. I just threw some numbers together. But it allows for complex mathematical statements to be expressed concisely. It should be noted here that any later work in C# will not inherit these operators as cleanly, they would have to be called in method form with names similar to “op_Multiply” F# Operator Overloading

I’m sure there will be more methods to add to this type but I’ll add them when a clear need is shown. This is where I leave you for now. I’m going to go ahead and fill out “Vector2i” for integers before the next post.