packages feed

terminal-3d-graphics-0.2.0.0: src/Terminal3D/Matrix.hs

module Terminal3D.Matrix where

import Terminal3D.Vector

-- | A 4x4 Matrix.
data Mat4 = Mat4 Vec4 Vec4 Vec4 Vec4 deriving (Show, Eq)

-- | Transpose a 4x4 matrix
transposeMat4 :: Mat4 -> Mat4
transposeMat4 (Mat4 (Vec4 r1c1 r1c2 r1c3 r1c4)
                    (Vec4 r2c1 r2c2 r2c3 r2c4)
                    (Vec4 r3c1 r3c2 r3c3 r3c4)
                    (Vec4 r4c1 r4c2 r4c3 r4c4)) =
    Mat4
      (Vec4 r1c1 r2c1 r3c1 r4c1)
      (Vec4 r1c2 r2c2 r3c2 r4c2)
      (Vec4 r1c3 r2c3 r3c3 r4c3)
      (Vec4 r1c4 r2c4 r3c4 r4c4)

instance Semigroup Mat4 where
    (<>) (Mat4 r1 r2 r3 r4) m2 =
        let m2T = transposeMat4 m2
        in Mat4 (multMatVec m2T r1)
                (multMatVec m2T r2)
                (multMatVec m2T r3)
                (multMatVec m2T r4)

instance Monoid Mat4 where
    mempty = Mat4
        (Vec4 1 0 0 0)
        (Vec4 0 1 0 0)
        (Vec4 0 0 1 0)
        (Vec4 0 0 0 1)

-- | Multiply a 4x4 matrix by a column Vec4
multMatVec :: Mat4 -> Vec4 -> Vec4
multMatVec (Mat4 r1 r2 r3 r4) colV =
    Vec4 (dot r1 colV) (dot r2 colV) (dot r3 colV) (dot r4 colV)

-- | Perspective-divide: divide x, y, z by w
divW :: Vec4 -> Vec4
divW (Vec4 x y z w) = Vec4 (x/w) (y/w) (z/w) w

-- | Apply a rotation matrix to a Vec3 (homogeneous)
rotateWorld :: Mat4 -> Vec3 -> Vec3
rotateWorld m (Vec3 a b c) = toVec3 (divW (multMatVec m (Vec4 a b c 1)))

-- | Build a perspective matrix that respects the screen's aspect ratio
screenPerspectiveMatrix :: (Int, Int) -> Double -> Double -> Mat4
screenPerspectiveMatrix (screenX, screenY) n f =
    let ratio = fromIntegral screenY / fromIntegral screenX
        (right, top) = if screenX > screenY then (1, ratio) else (1/ratio, 1)
    in symmetricPerspectiveMatrix right n top f

-- | Symmetric perspective matrix (see https://www.mauriciopoppe.com/notes/computer-graphics/viewing/projection-transform/ Eq. 12)
symmetricPerspectiveMatrix :: Double -> Double -> Double -> Double -> Mat4
symmetricPerspectiveMatrix r n t f = Mat4
    (Vec4 (n/r) 0     0             0            )
    (Vec4 0     (n/t) 0             0            )
    (Vec4 0     0     ((f+n)/(n-f)) (2*f*n/(n-f)))
    (Vec4 0     0     (-1)          0            )

-- | Rotation matrix around the X axis (pitch)
pitchMatrix :: Double -> Mat4
pitchMatrix a = Mat4
    (Vec4 1       0        0 0)
    (Vec4 0 (cos a) (-sin a) 0)
    (Vec4 0 (sin a)  (cos a) 0)
    (Vec4 0       0        0 1)

-- | Rotation matrix around the Y axis (yaw)
yawMatrix :: Double -> Mat4
yawMatrix a = Mat4
    (Vec4  ( cos a) 0 ( sin a) 0)
    (Vec4        0  1       0  0)
    (Vec4 (-sin a)  0 ( cos a) 0)
    (Vec4        0  0       0  1)

-- | Rotation matrix around the Z axis (roll)
rollMatrix :: Double -> Mat4
rollMatrix a = Mat4
    (Vec4 (cos a) (-sin a) 0 0)
    (Vec4 (sin a)  (cos a) 0 0)
    (Vec4       0        0 1 0)
    (Vec4       0        0 0 1)

-- | Combined pitch/yaw/roll rotation matrix from a Vec3 of Euler angles
rotationMatrix :: Vec3 -> Mat4
rotationMatrix (Vec3 pitch yaw roll) =
    mconcat [pitchMatrix pitch, yawMatrix yaw, rollMatrix roll]