brillo-examples-2.0.0: picture/Gravity/Main.hs
module Main where
import Brillo
import Brillo.Interface.Environment
import System.Random
-- x, y, dx, dy
type Particle =
(Float, Float, Float, Float)
main :: IO ()
main =
do
g <- getStdGen
(width, height) <- getScreenSize
let initialstate = generateParticles g width height
simulate window background fps initialstate render update
where
window = FullScreen
background = black
fps = 60
render xs = Pictures $ map particleImage xs
update _ = updateParticles
-- | Generates particles from StdGen
generateParticles :: StdGen -> Int -> Int -> [Particle]
generateParticles gen widthInt heightInt =
map (g . f) tups
where
-- change range [0,1] -> [-s/2,s/2]
f = \(x, y) -> (x * width - width / 2, y * height - height / 2)
-- add speed of 0
g = \(x, y) -> (x, y, 0, 0)
-- 200 Random float tuples
tups = take 50 $ zip randoms1 randoms2
randoms1 = randoms gen1 :: [Float]
randoms2 = randoms gen2 :: [Float]
(gen1, gen2) = split gen
width = toEnum widthInt
height = toEnum heightInt
-- | Particle to its picture
particleImage :: Particle -> Picture
particleImage (x, y, _, _) =
Translate x y $ Color white $ circleSolid 2
-- | To update particles for next frame
updateParticles :: Float -> [Particle] -> [Particle]
updateParticles dt =
(accelerateParticles dt) . (moveParticles dt)
-- | Moves particles based on their speed
moveParticles :: Float -> [Particle] -> [Particle]
moveParticles dt =
map (\(x, y, dx, dy) -> (x + dx * dt, y + dy * dt, dx, dy))
-- | Accelerates particles based on gravity
accelerateParticles :: Float -> [Particle] -> [Particle]
accelerateParticles dt ps =
map (gravitate ps dt) ps
{-| Given particles to be gravitating to and for how long,
updates a single particle's speed
-}
gravitate :: [Particle] -> Float -> Particle -> Particle
gravitate [] _ p = p
gravitate ((x', y', _, _) : ps) dt p@(x, y, dx, dy) =
-- To dodge divByZero or near divByZero anomalies
if separated x x' && separated y y'
then gravitate ps dt p'
else gravitate ps dt p
where
p' = (x, y, dx + ddx, dy + ddy)
ddx = dirx * g
ddy = diry * g
(dirx, diry) = direction (x, y) (x', y')
g = gravitation (x, y) (x', y')
-- | Normalized vector from one point to another.
direction :: (Float, Float) -> (Float, Float) -> (Float, Float)
direction (x, y) (x', y') =
(dx * scale', dy * scale')
where
dx = x' - x
dy = y' - y
scale' = 1 / sqrt (dx ^ (2 :: Int) + dy ^ (2 :: Int))
-- | Checks if floats not too close to each other
separated :: Float -> Float -> Bool
separated x y =
0.001 < abs (x - y)
-- | Gravitational force of one particle to another
gravitation :: (Float, Float) -> (Float, Float) -> Float
gravitation (x, y) (x', y') =
g / sqrt (dx ^ (2 :: Int) + dy ^ (2 :: Int))
where
dx = x' - x
dy = y' - y
g = 1