{-# LANGUAGE BangPatterns #-} module Geometry.Vector where import Geometry.Data {- | Moves from two to three dimensions, adding zero in z direction. -} zeroZ :: Point2 -> Point3 {-# INLINE zeroZ #-} zeroZ (V2 x y) = V3 x y 0 infixl 6 +.+, -.- infixl 7 *.* {- | 2D coordinate-wise addition. -} (+.+) :: Point2 -> Point2 -> Point2 {-# INLINE (+.+) #-} --(+.+) = -- (+) V2 x1 y1 +.+ V2 x2 y2 = let !x = x1 + x2 !y = y1 + y2 in V2 x y {- | 2D coordinate-wise subtraction. -} (-.-) :: Point2 -> Point2 -> Point2 {-# INLINE (-.-) #-} --(-.-) = (-) V2 x1 y1 -.- V2 x2 y2 = let !x = x1 - x2 !y = y1 - y2 in V2 x y {- | 2D scalar multiplication. -} (*.*) :: Float -> Point2 -> Point2 {-# INLINE (*.*) #-} a *.* V2 x2 y2 = let !x = a * x2 !y = a * y2 in V2 x y {- | Normalize a vector to length 1. -} normalizeV :: Point2 -> Point2 {-# INLINE normalizeV #-} normalizeV p = (1 / magV p) *.* p {- | Angle between two vectors. Always positive. -} angleVV :: Point2 -> Point2 -> Float {-# INLINE angleVV #-} angleVV a b | a == b = 0 | otherwise = let ma = magV a mb = magV b d = a `dotV` b in acos $ d / (ma * mb) {- | Safe version of 'angleVV' that returns 0 if either vector is null. -} safeAngleVV :: Point2 -> Point2 -> Float {-# INLINE safeAngleVV #-} safeAngleVV a b | a == V2 0 0 || b == V2 0 0 = 0 | otherwise = angleVV a b {- | Dot product. -} dotV :: Point2 -> Point2 -> Float {-# INLINE dotV #-} dotV (V2 x y) (V2 z w) = x*z + y*w {- | Given vector, returns the angle, anticlockwise from +ve x-axis, in radians. -} argV :: Point2 -> Float {-# INLINE argV #-} argV (V2 x y) = normalizeAngle $ atan2 y x {- | Determinant of the matrix formed by two vectors. -} detV :: Point2 -> Point2 -> Float {-# INLINE detV #-} detV (V2 x1 y1) (V2 x2 y2) = x1 * y2 - y1 * x2 {- | Given an angle in radians, anticlockwise from +ve x-axis, - returns the corresponding unit vector. -} unitVectorAtAngle :: Float -> Point2 {-# INLINE unitVectorAtAngle #-} unitVectorAtAngle r = V2 (cos r) (sin r) -- | Rotate a vector by an angle (in radians). +ve angle is counter-clockwise. rotateV :: Float -> Point2 -> Point2 rotateV r (V2 x y) = V2 (x * cos r - y * sin r) (x * sin r + y * cos r) {-# INLINE rotateV #-} -- | Convert degrees to radians degToRad :: Float -> Float degToRad d = d * pi / 180 {-# INLINE degToRad #-} -- | Convert radians to degrees radToDeg :: Float -> Float radToDeg r = r * 180 / pi {-# INLINE radToDeg #-} -- | Normalize an angle to be between 0 and 2*pi radians normalizeAngle :: Float -> Float {-# INLINE normalizeAngle #-} normalizeAngle f | f >= 0 && f < 2*pi = f | otherwise = f - 2 * pi * floor' (f / (2 * pi)) where floor' :: Float -> Float floor' x = fromIntegral (floor x :: Int) {- | Rotate vector by pi/2 clockwise. -} vNormal :: Point2 -> Point2 {-# INLINE vNormal #-} vNormal (V2 x y) = V2 y (negate x) {- | Negate a vector. -} vInverse :: Point2 -> Point2 vInverse (V2 x y) = V2 (-x) (-y) {- | Normalize a vector safely: on (0,0) return (0,0). -} safeNormalizeV :: Point2 -> Point2 {-# INLINE safeNormalizeV #-} safeNormalizeV (V2 0 0) = V2 0 0 safeNormalizeV p = (1/magV p ) *.* p {- | Magnitude of a vector. -} magV :: Point2 -> Float {-# INLINE magV #-} magV (V2 x y) = sqrt $ x^(2::Int) + y^(2::Int) {- | Magnitude of the cross product of two vectors. Identical to detV. -} crossV :: Point2 -> Point2 -> Float crossV (V2 ax ay) (V2 bx by) = ax*by - ay*bx {- | TO CHECK Orthographic projection of one vector onto another. -} projV :: Point2 -> Point2 -> Point2 projV fromv onv | den == 0 = error "tried projecting onto zero vector" | otherwise = (fromv `dotV` onv) / den *.* onv where den = onv `dotV` onv -- | Return distance between two points. dist :: Point2 -> Point2 -> Float {-# INLINE dist #-} dist !p1 !p2 = magV (p2 -.- p1) -- | Finds a new angle a given fraction between two other angles tweenAngles :: Float -> Float -> Float -> Float {-# INLINE tweenAngles #-} tweenAngles frac a1 a2 | abs (a1 - a2) < pi = frac * (a1 - a2) + a2 | otherwise = normalizeAngle $ go frac a1 a2 where go frac' a1' a2' | abs (a1' - a2') < pi = frac' * (a1' - a2') + a2' | a1' > a2' = go frac' (a1' - 2*pi) a2' | otherwise = go frac' a1' (a2' - 2*pi)