Fork me on GitHub
Source file: 3d-vectors.fut

# Three-dimensional vectors

This example shows how we might define a module type for programming with vectors in normal 3D space.

``module type vspace_3d = {``

We keep the type of vector components abstract, because it will be useful to have the same interface for vectors with components that are `f32`, `f64`, or something even more exotic.

``  type scalar``

However, the vector type itself is opaque. It must be a record with `x`/`y`/`z` fields.

``  type vector = {x: scalar, y: scalar, z: scalar}``

Vectors must support the usual binary operations.

``````  val +: vector -> vector -> vector
val -: vector -> vector -> vector
val *: vector -> vector -> vector
val /: vector -> vector -> vector``````

As well as dot products and cross products.

``````  val dot: vector -> vector -> scalar
val cross: vector -> vector -> vector``````

Vectors can be scaled by a scalar.

``  val scale: scalar -> vector -> vector``

We can also take the length of a vector, as well as normalise a vector to have unit length.

``````  val length: vector -> scalar
val normalise: vector -> vector``````

Finally, vectors can be rotated around the three axes.

``````  val rot_x : (radians: scalar) -> vector -> vector
val rot_y : (radians: scalar) -> vector -> vector
val rot_z : (radians: scalar) -> vector -> vector

}``````

So for which scalars can this module type be implemented? We describe this with yet another module type.

``````module type scalar = {
type t

val +: t -> t -> t
val -: t -> t -> t
val *: t -> t -> t
val /: t -> t -> t

val f64: f64 -> t
val sqrt : t -> t
val cos : t -> t
val sin : t -> t
}``````

Because of Futhark’s structural type system, the built-in modules `f32` and `f64` already satisfy this interface.

We can now define a parametric module for constructing vector spaces:

``````module mk_vspace_3d(P: scalar): vspace_3d with scalar = P.t = {
type scalar = P.t

type vector = {x: scalar, y: scalar, z: scalar}

def zero = {x = P.f64 0, y = P.f64 0, z = P.f64 0}
def one  = P.f64 1``````

Most of the definitions are straight out of a textbook, and so we won’t be providing much commentary.

We start out by defining two helper functions for doing operations on the components of a vector.

``````  def map (f: scalar -> scalar) (v : vector) =
{x = f v.x, y = f v.y, z = f v.z}

def map2 (f: scalar -> scalar -> scalar) (a : vector) (b : vector) =
{x = f a.x b.x, y = f a.y b.y, z = f a.z b.z}``````

This allows us to conveniently define vector arithmetic and scaling.

``````  def (+) = map2 (P.+)
def (-) = map2 (P.-)
def (*) = map2 (P.*)
def (/) = map2 (P./)
def scale (s: scalar) = map (s P.*)``````

The remaining operations are defined explicitly.

``````  def dot (a: vector) (b: vector) =
P.(a.x*b.x + a.y*b.y + a.z*b.z)

def cross ({x=ax,y=ay,z=az}: vector)
({x=bx,y=by,z=bz}: vector): vector =
P.({x=ay*bz-az*by, y=az*bx-ax*bz, z=ax*by-ay*bx})

def length v = P.sqrt (dot v v)

def normalise (v: vector): vector =
let l = length v
in scale (one P./ l) v

def rot_x (theta: scalar) ({x,y,z} : vector) =
let cos_theta = P.cos theta
let sin_theta = P.sin theta
in { x
, y = P.(cos_theta * y - sin_theta * z)
, z = P.(sin_theta * y + cos_theta * z)}

def rot_y (theta: scalar) ({x,y,z} : vector) =
let cos_theta = P.cos theta
let sin_theta = P.sin theta
in { x = P.(cos_theta * x - sin_theta * z)
, y
, z = P.(sin_theta * x + cos_theta * z)}

def rot_z (theta: scalar) ({x,y,z} : vector) =
let cos_theta = P.cos theta
let sin_theta = P.sin theta
in { x = P.(cos_theta * x - sin_theta * y)
, y = P.(sin_theta * x + cos_theta * y)
, z}
}``````

Instantiating the module:

``module f64_3d = mk_vspace_3d f64``

And trying it out:

``````> f64_3d.cross {x=1,y=2,z=3} {x=4,y=5,z=6}
{x = -3.0f64, y = 6.0f64, z = -3.0f64}``````

# See also

Dex: Multi-step ray tracer

The vector package.