about summary refs log tree commit diff
path: root/grav1ty.hs
blob: 2a8ac8db0e49ca253468492de98ac29ba2782fe2 (plain) (blame)
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
{-# LANGUAGE TemplateHaskell #-}
import Linear.V2
import Linear.Vector
import Linear.Metric
import Debug.Trace
import Control.FRPNow hiding ((*^), (^/), norm)
import Control.FRPNow.Gloss
import Graphics.Gloss
import Control.Lens

fps :: Num a => a
fps = 30

-- | Representation of the objects in this simulation.
--   An Object is either a static object or a dynamic
--   object which gets influenced by the gravity force
--   of other objects.
--   Dynamic objects may but don't have to be controlled
--   by an user. Also multiplayer scenarios are possible.
--   By control I mean controlling the manual acceleration
--   _acc.
data Object
  = Static {
    _loc  :: V2 Float -- ^ location of the object (V2 0 0) is in the centre
                      --   of the simulation
  , _pic  :: Picture  -- ^ the gloss picture to draw the object
  , _mass :: Float    -- ^ mass in kilograms
  }
  | Dynamic {
    _loc  :: V2 Float -- ^ location of the object (V2 0 0) is in the centre
                      --   of the simulation
  , _pic  :: Picture  -- ^ the gloss picture to draw the object
  , _mass :: Float    -- ^ mass in kilograms
  , _acc  :: V2 Float -- ^ current manual acceleration of the object. Think of
                      --    this as the acceleration caused by a spaceship's
                      --    thrusters or similar.
  } deriving (Eq, Show)

makeLenses ''Object

type Universe = [Object]

data CompSt
  = MkCompSt {
    _lastUni :: Universe
  , _currUni :: Universe
  }

takePicture :: Object -> Picture
takePicture obj = let (V2 x y) = _loc obj
                      pic      = _pic obj
                    in translate x y pic

dropFirst :: Eq a => a -> [a] -> [a]
dropFirst _ []     = []
dropFirst x (y:ys) = if x == y then ys else dropFirst x ys

toTuple :: V2 a -> (a, a)
toTuple (V2 x y) = (x, y)

fromTuple :: (a, a) -> V2 a
fromTuple (x, y) = V2 x y

scaleBoth :: Float -> Picture -> Picture
scaleBoth f = scale f f

timeStep :: Fractional a => a
timeStep = 1 / fps

unitChange :: Eq a => Behavior a -> Behavior (Event ())
unitChange behavior = (() <$) <$> change behavior

-- TODO: think about *best* approximation
nextUniverse :: Universe -> Universe -> Universe
nextUniverse last curr = next last curr last curr
  where next last curr (l:ls) (c:cs)
          = nextObject l c (gravityForces c curr)
            : next last curr ls cs
        next _ _ [] [] = []

nextObject :: Object -> Object -> [V2 Float] -> Object
nextObject last@(Static {}) curr@(Static {})   _ = curr
nextObject last@(Dynamic {}) curr@(Dynamic {}) forces
  = curr & loc .~
      2 * _loc curr - _loc last + (timeStep ** 2) *^ (_acc curr + sum forces)

gravityForce :: Object -> Object -> V2 Float
gravityForce a b = (gravityConst * ((_mass a * _mass b) / (absDistance ** 2)))
  *^ (distance ^/ absDistance)
  where gravityConst = 6.67408e-11
        distance = _loc b - _loc a
        absDistance = norm distance

gravityForces :: Object -> Universe -> [V2 Float]
gravityForces obj uni = map (gravityForce obj) $ dropFirst obj uni

universeB :: CompSt
          -> Behavior Time
          -> Behavior (V2 Float)
          -> Behavior (Behavior Universe)
universeB state time accB = do
  let newState = MkCompSt {
        _lastUni = _currUni state
      , _currUni = nextUniverse (_lastUni state) (_currUni state)
      }
  e <- sample $ unitChange time
  e' <- snapshot (universeB newState time accB) e
  return $ pure (_currUni state) `switch` e'

exampleUniverse :: Universe
exampleUniverse =
  [ Dynamic (V2 0 (earthRadius + 100000)) (color (makeColor 1 0 0 1) $ circleSolid (earthRadius / 100)) spaceshipMass (V2 0 0)
  , Static (V2 0 0) (color (makeColor 0 0 1 1) $ circleSolid (earthRadius)) earthMass
  ]
  where earthMass = 5.974e24
        earthRadius = 6378137
        spaceshipMass = 1935


mainFRP :: Universe
        -> Behavior Time
        -> EvStream GEvent
        -> Behavior (Behavior Picture)
mainFRP uni time evs = do
  let initialState = MkCompSt uni uni
  uniB <- universeB initialState time $ pure (V2 0 0)
  return $ pictures . map (scaleBoth 0.00005 . takePicture) <$> uniB

main :: IO ()
main = runNowGlossPure (InWindow "grav1ty" (800,600) (10,10)) white fps
  (mainFRP exampleUniverse)