Recently I bought Simon's course on Math for Game Developers and as I finally found some time to go through the first couple of chapters I would like to share my lecture notes here. I used Racket for mathematical functions and comments for plain English, a lot of stuff is also borrowed from the wiki page on Euclidean vectors.
#lang racket
(require rackunit)
;; Euclidean space is the mathematical formalization of the "flat"
;; geometry we learned in school where for example:
;; * parallel lines never meet
;; * angles in triangle sum to 180°
;; * Pythagorean theorem holds.. etc
;; Cartesian coordinate system is the standard way to represent
;; Euclidean space numerically using perpendicular axes x y z
;; to define positions in space
;; Simple "number line" is way to represent a one-dimensional Euclidean space
;; Point / Position / Position Vector in gamedev all denote the same idea:
;; representation of a specific location in the virtual space
;; Vector = Magnitude (length or size) + Direction
;; Points are written with just letters; Vectors have a little arrow on top
;; Numerically they are represented exactly the same but mean different things
(test-case "Point vs Vector"
;; thermometer example: one-dimensional space
(define current 5) ; current temperature (point, initial state)
(define forecast 2) ; "2 degrees warmer" change (vector)
;;; point + vector = point (also point - vector = point)
(check-equal? (+ current forecast) 7)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define A 10) ; temperature now (point)
(define B 15) ; temperature later (point)
;;; point - point = vector
(check-equal? (- B A) 5)
;;; (point + point) doesn't make much sense
)
;; Two vectors are considered equal
;; if they have the same magnitude and direction = if their coordinates are equal
(test-case "Equality"
(define a #(1 2 3))
(define b #(1 2 3))
(check-equal? a b))
;; contracts from now on should help to distinguish
;; points/vectors (numvector?) from scalars (number?)
;; those are more strict contracts for two- and three-dimensional vectors
(define (numvector? v)
(and (vector? v)
(for/and ([x (in-vector v)]) (number? x))))
(define vector2/c (vector/c number? number?))
(define vector3/c (vector/c number? number? number?))
;; To add the vectors we add the corresponding components from each vector
(define/contract (add a b)
(-> numvector? numvector? numvector?)
(vector-map + a b))
(define/contract (sub a b)
(-> numvector? numvector? numvector?)
(vector-map - a b))
(test-case "Addition and subtraction"
(define a #(1 2 3))
(define b #(1 2 3))
(check-equal? (add a b) #(2 4 6))
(check-equal? (sub a b) #(0 0 0)))
(define (random-vector3) (vector (random 10) (random 10) (random 10)))
;; (a + b) + c = a + (b + c)
(test-case "Associativity"
(define a (random-vector3))
(define b (random-vector3))
(define c (random-vector3))
(check-equal? (add (add a b) c) (add a (add b c))))
;; a + b = b + a
(test-case "Commutativity"
(define a (random-vector3))
(define b (random-vector3))
(check-equal? (add a b) (add b a)))
;; Vector subtraction is neither associative nor commutative!
;; Vector length with Pythagorean theorem
;; Vector's v magnitude is denoted as ‖v‖
(define/contract (magnitude v)
(-> numvector? number?)
(sqrt (for/fold ([acc 0]) ([x (in-vector v)]) (+ acc (* x x)))))
;; Here 'from' and 'to' are not vectors but points!
(define/contract (distance from to)
(-> numvector? numvector? number?)
(magnitude (sub to from)))
(test-case "Distance"
(check-equal? (magnitude #(2 0)) 2)
(check-equal? (magnitude #(0 3)) 3)
(check-equal? (magnitude #(3 4)) 5)
(check-equal? (distance #(3 4) #(0 0)) 5)
(check-equal? (magnitude #(3 4 12)) 13))
;; Scalar multiplication of vector V and scalar S written as SV
(define/contract (scale v s)
(-> numvector? number? numvector?)
(vector-map (curry * s) v))
;; Hadamard Product, written as a ∘ b
;; just an element-wise multiplication
;; could be used for non-uniform scale!
(define/contract (hadamard a b)
(-> numvector? numvector? numvector?)
(vector-map * a b))
;; Both scale functions (scalar and component-wise) are
;; Commutative and associative - order doesn't matter
(test-case "Vector scaling"
;; multiplying with a scalar changes magnitude, same direction
(check-equal? (scale #(3 4) 4) #(12 16))
;; negative scalar reverses direction
(check-equal? (scale #(1 2) -1.5) #(-1.5 -3.0))
;; RGB color can be represented as three-dimensional vector!
;; Hadamard Product could be used to tint color by other color
(define/contract (tint-red color)
(-> vector3/c vector3/c)
(define red-mask #(1 0 0))
(hadamard color red-mask))
(check-equal? (scale #(1 2 3) 5) #(5 10 15))
(check-equal? (hadamard #(3 4) #(1 0)) #(3 0))
(check-equal? (hadamard #(5 6) #(5 5)) #(25 30))
(check-equal? (hadamard #(1 2 3) #(1 -1 1)) #(1 -2 3)))
;; Unit vector is any vector with a length of one, indicates direction
;; denoted by a lowercase letter with a circumflex or "hat" ^
;; Vector can be normalized or divided by its length to create a unit vector
(define/contract (normalize v)
(-> numvector? numvector?)
(scale v (/ 1 (magnitude v))))
(test-case "Normalized Vectors"
(check-equal? (normalize #(3 4)) #(3/5 4/5))
(check-equal? (normalize #(0.75 0.25)) (normalize #(3 1))))
;; Dot Product, Scalar projection, Scalar product
;; also ‖a‖ ‖b‖ cosθ where θ is an angle between a and b
;; if both normalized gives us a way to say how similar vectors are
(define/contract (dot a b)
(-> numvector? numvector? number?)
(for/fold ([acc 0]) ([x (in-vector a)] [y (in-vector b)]) (+ acc (* x y))))
(test-case "Dot Product"
;; Orthogonal! Perpendicular to each other, θ = π/2, cosθ = 0
(check-equal? (dot #(1 0) #(0 1)) 0)
(check-equal? (dot #(1 0) #(0 -1)) 0)
;; Opposite direction
(check-equal? (dot #(1 0) #(-1 0)) -1)
;; Same direction
(check-equal? (dot #(1 0) #(1 0)) 1)
;; dot product of a vector with itself is equal to
;; the square of the magnitude
(define a (random-vector3))
(define ε 1e-12)
(check-= (dot a a) (expt (magnitude a) 2) ε))
;; Vector (a x b) is perpendicular to both a and b and the plane formed by them
;; Its magnitude represents the area of the parallelogram spanned by a and b
(define/contract (cross a b)
(-> vector3/c vector3/c vector3/c)
(match-let ([(vector ax ay az) a]
[(vector bx by bz) b])
(vector (- (* ay bz) (* az by))
(- (* az bx) (* ax bz))
(- (* ax by) (* ay bx)))))
(test-case "Cross product"
(check-equal? (cross #(1 0 0) #(0 1 0)) #(0 0 1))
;; Order matters! Changing order reverses the direction
(check-equal? (cross #(0 1 0) #(1 0 0)) #(0 0 -1))
;; For parallel vectors a x b results in the zero vector
(check-equal? (cross #(1 1 1) #(2 2 2)) #(0 0 0))
;; If either a or b are zero vector, a x b is also zero vector
(check-equal? (cross #(1 2 3) #(0 0 0)) #(0 0 0))
(check-equal? (cross #(0 0 0) #(1 2 3)) #(0 0 0)))
(provide numvector? vector2/c vector3/c magnitude dot)