summaryrefslogtreecommitdiff
path: root/Camera.hs
blob: 6bf4dd852bd68d9028a743c7cdbe5d26ea035f13 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
module Camera where

import qualified Data.Vector.Generic as G
import Numeric.LinearAlgebra as Math hiding ((<>))

import Matrix
import LambdaCube.GL.HMatrix ()

data Camera = Camera
    { camHeightAngle   :: Float
    , camTarget        :: Vector Float -- 3-vector
    , camDirection     :: Vector Float -- 3-vector
    , camDistance      :: Float
    , camWidth         :: Float
    , camHeight        :: Float
    , camUp            :: Vector Float -- 3-vector
    , camWorldToScreen :: Maybe (Matrix Float) -- 4×4
    , camScreenToWorld :: Maybe (Matrix Float) -- 4×4
    }

-- | Compute the height of a pixel at the given 3d point.
pixelDelta :: Camera -> Vector Float -> Float
pixelDelta cam x = realToFrac $ frustumHeight eyeToPoint / realToFrac (camHeight cam)
 where
    eyeToPoint = norm_2 (x - camPos cam)
    frustumHeight d = 2 * d * tan (realToFrac $ camHeightAngle cam / 2)

camPos :: Camera -> Vector Float
camPos c = camTarget c - scale (camDistance c) (camDirection c)

camWorldCoordinates :: Camera -> Double -> Double -> Maybe (Vector Float) -> Vector Float
camWorldCoordinates cam h k mplane = case mplane of
        -- Write on the plane.
        Just plane -> let n̂ = G.init plane
                          c = plane!3
                          a = (c - dot p n̂) / dot d̂ n̂
                       in p + scale a d̂

        -- Write on the camDistance sphere.
        Nothing    -> p + scale (camDistance cam) d̂
 where
    q0 = fromList [ 2 * realToFrac h/camWidth cam - 1
                  , 1 - 2 * realToFrac k/camHeight cam
                  , 1
                  , 1
                  ] :: Vector Float
    pv = snd $ projectionView cam
    q1 = pv #> q0
    q2 = scale (1 /(q1!3)) $ G.init q1
    p = camPos cam
    d = q2 - p
    d̂ = unit d

projectionView :: Camera -> (Camera,Matrix Float)
projectionView c
    | Just m <- camScreenToWorld c = (c,m)
    | Just w <- camWorldToScreen c = projectionView c{ camScreenToWorld = Just $ invFloat w }
    | otherwise                    = projectionView $ fst $ viewProjection c

viewProjection :: Camera -> (Camera,(Matrix Float,Vector Float))
viewProjection c
    | Just m <- camWorldToScreen c = (c,(m,pos))
    | otherwise                    = (c { camWorldToScreen = Just m' }, (m',pos))
 where
    m' = proj <> cam
    cam = lookat pos (camTarget c) (camUp c)
    pos = camPos c
    proj = perspective 0.1 100 (camHeightAngle c) (camWidth c / camHeight c)

invFloat :: Matrix Float -> Matrix Float
invFloat m = realToFracMatrix $ inv (realToFracMatrix m :: Matrix Double)

realToFracMatrix :: (Real a, Fractional t, Element t, Element a) => Matrix a -> Matrix t
realToFracMatrix m = fromLists $ map realToFrac <$> toLists m

unit :: (Linear t c, Fractional t, Normed (c t)) => c t -> c t
unit v = scale (1/realToFrac (norm_2 v)) v