-- | A 'Camera' represents a coordinate frame into which 3D points may -- be transformed. For rendering purposes, it is often helpful to -- combine a transformation matrix computed from a 'Camera' by -- 'camMatrix' -- that transforms points into the camera's coordinate -- frame -- with a perspective projection matrix, as created by -- 'projectionMatrix'. module Graphics.GLUtil.Camera3D (-- * Camera movement Camera(..), panRad, pan, tiltRad, tilt, rollRad, roll, dolly, -- * Camera initialization rosCamera, fpsCamera, -- * Matrices projectionMatrix, camMatrix, -- * Miscellaneous deg2rad) where import Linear (Conjugate(conjugate), Epsilon, V3(..), V4(..)) import Linear.Matrix (mkTransformation, M44) import Linear.Quaternion (Quaternion, axisAngle, rotate) -- | A 'Camera' may be translated and rotated to provide a coordinate -- frame into which 3D points may be transformed. data Camera a = Camera { forward :: V3 a , upward :: V3 a , rightward :: V3 a , orientation :: Quaternion a , location :: V3 a } -- | Pan a camera view (turn side-to-side) by an angle given in -- radians. Panning is about the world's up-axis as captured by the -- initial camera state (e.g. the positive Y axis for 'fpsCamera'). panRad :: (Epsilon a, RealFloat a) => a -> Camera a -> Camera a panRad theta c = c { orientation = r * orientation c } where r = axisAngle (upward c) theta -- | Pan a camera view (turn side-to-side) by an angle given in -- degrees. Panning is about the world's up-axis as captured by the -- initial camera state (e.g. the positive Y axis for 'fpsCamera'). pan :: (Epsilon a, RealFloat a) => a -> Camera a -> Camera a pan = panRad . deg2rad -- | Tilt a camera view (up-and-down) by an angle given in -- radians. Tilting is about the camera's horizontal axis (e.g. the -- positive X axis for 'fpsCamera'). tiltRad :: (Epsilon a, RealFloat a) => a -> Camera a -> Camera a tiltRad theta c = c { orientation = orientation c * r } where r = axisAngle (rightward c) theta -- | Tilt a camera view (up-and-down) by an angle given in degrees. -- Tilting is about the camera's horizontal axis (e.g. the positive X -- axis for 'fpsCamera'). tilt :: (Epsilon a, RealFloat a) => a -> Camera a -> Camera a tilt = tiltRad . deg2rad -- | Roll a camera view about its view direction by an angle given in -- radians. Rolling is about the camera's forward axis (e.g. the -- negative Z axis for 'fpsCamera'). rollRad :: (Epsilon a, RealFloat a) => a -> Camera a -> Camera a rollRad theta c = c { orientation = orientation c * r } where r = axisAngle (forward c) theta -- | Roll a camera view about its view direction by an angle given in -- degrees. Rolling is about the camera's forward axis (e.g. the -- negative Z axis for 'fpsCamera'). roll :: (Epsilon a, RealFloat a) => a -> Camera a -> Camera a roll = rollRad . deg2rad -- | Translate a camera's position by the given vector. dolly :: (Conjugate a, Epsilon a, RealFloat a) => V3 a -> Camera a -> Camera a dolly t c = c { location = location c + t' } where t' = orientation c `rotate` t -- | Convert degrees to radians. deg2rad :: RealFloat a => a -> a deg2rad x = x * pi / 180 -- | A camera at the origin with its up-axis coincident with the -- positive Z axis. This is the convention used by the ROS robotics -- platform. rosCamera :: (Epsilon a, RealFloat a) => Camera a rosCamera = Camera (V3 1 0 0) (V3 0 0 1) (V3 0 1 0) 1 0 -- | A camera at the origin with its up-axis coincident with the -- positive Y axis. This is the convention used by "first-person -- shooter" (fps) video games. fpsCamera :: (Epsilon a, RealFloat a) => Camera a fpsCamera = Camera (V3 0 0 (-1)) (V3 0 1 0) (V3 1 0 0) 1 0 -- | @projectionMatrix fov aspect near far@ produces a perspective -- projection matrix with the specified vertical field of view (FOV), -- given in radians, aspect ratio, and near and far clipping planes. projectionMatrix :: (Conjugate a, Epsilon a, RealFloat a) => a -> a -> a -> a -> M44 a projectionMatrix fovy aspect near far = V4 (V4 (focal / aspect) 0 0 0) (V4 0 focal 0 0) (V4 0 0 ((far+near) / (near - far)) ((2*far*near) / (near - far))) (V4 0 0 (-1) 0) where focal = 1 / tan (fovy * 0.5) -- | Produce a transformation matrix from a 'Camera'. This matrix -- transforms homogenous points into the camera's coordinate frame. camMatrix :: (Conjugate a, Epsilon a, RealFloat a) => Camera a -> M44 a camMatrix c = mkTransformation q (rotate q . negate . location $ c) where q = conjugate $ orientation c {- -- | A lens for the fourth column of a matrix. translation' :: (R3 t, R4 v, Functor f) => (V3 a -> f (V3 a)) -> t (v a) -> f (t (v a)) translation' f m = fmap (\(V3 x y z) -> m & _x._w .~ x & _y._w .~ y & _z._w .~ z) (f (fmap (^. _w) (m ^. _xyz))) -}