rakshit

Tuples and Vectors

The first chapter of the book deals with writing a data structure called Tuple. Tuples are used as a way to represent points and vectors.

To create the data type of tuple, I defined the struct as follows:

struct Tuple {
    x: f32,
    y: f32,
    z: f32,
    w: f32
}

While x, y and z are cartesian coordinates, w is used to identify between a vector and a point. For a vector, w is set to 0 and for a point, it is set to 1.

Afterwards, numerous operations are written:

• addition of vectors • subtraction of vectors, and subtraction of points which lead to a vector • negation of points and vectors • the dot product of vectors • the cross product of vectors • the magnitude of a vector • normalising a vector

Overloading is not a concept in Rust, therefore to implement these operations I rely on traits. Traits are defined abstractly, but not implemented. They are implemented differently for each struct.

Addition

I implement the addition operation as follows

impl Add for Tuple {
    type Output = Tuple;
    fn add(self, other: Tuple) -> Tuple {
        Tuple {
            x: self.x + other.x,
            y: self.y + other.y,
            z: self.z + other.z,
            w: self.w + other.w,
        }
    }
} 

Subtraction

impl Sub for Tuple {
    type Output = Tuple;
    fn sub(self, other: Tuple) -> Tuple {
        Tuple {
            x: self.x - other.x,
            y: self.y - other.y,
            z: self.z - other.z,
            w: self.w - other.w,
        }
    }
}

I implement the subtraction operation in a similar way to how the addition operation was implemented.

Negation

impl Neg for Tuple {
    type Output = Tuple;
    fn neg(self) -> Self::Output {
        Tuple {
            x: -self.x,
            y: -self.y,
            z: -self.z,
            w: -self.w
        }
    }
}

Scalar Multiplication with Vector

impl Mul<f32> for Tuple {
    type Output = Tuple;
    fn mul (self, rhs: f32) -> Tuple {
        Tuple { x: rhs*self.x,  y: rhs*self.y, z: rhs*self.z, w: rhs*self.w }
    }
}

This seems a bit different as here the multiplication trait is informed that a float value will be multiplied with this Tuple object and thus the slight change in syntax.

Dot Product

impl Mul<Tuple> for Tuple {
    type Output = f32;
    fn mul (self, other: Tuple) -> f32 {
        return self.x *other.x + self.y*other.y + self.z*other.z + self.w*other.w;
    }
}

The dot product is implemented similarly to scalar multiplication.

Cross Product

Previously, I was overloading the +, -, * signs. However, for the cross-product, I don't have an operation. I write a method which takes two vectors as input and outputs the cross-product vector.

fn cross_product(a: Tuple, b: Tuple) -> Tuple {
    Tuple { x: a.y * b.z - a.z * b.y, y: a.z * b.x - a.x * b.z, z:a.x * b.y - a.y * b.x, w: 0.0 }
}

Magnitude and Normalisation

As these are related to each vector, I added these functions to the implementation of the Tuple struct as follows:

impl Tuple {
    fn new(x: f32, y: f32, z: f32, w: f32) -> Self {
        Self {
            x: x,
            y: y,
            z: z,
            w: w,
        }
    }
    fn magnitude(&self) -> f32 {
        self.x*self.x + self.y*self.y +self.z*self.z + self.w*self.w
    }
    fn normalize(&self) -> Tuple {
        let mag = self.magnitude();
        Tuple { x: self.x/mag, y: self.y/mag, z: self.z/mag, w: self.w/mag }
    }
}

This concluded all the methods the chapter required us to write, however, an interesting point to note is that I don't have checks to see that operations such as cross-product, dot-product, magnitude and normalisation can only operate on vectors and not points. I currently avoid this and would return when I learn about error handling in Rust.