chiphunk-0.1.0.0: app/Main.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Chiphunk.Low
import Data.Functor
import Text.Printf (printf)
import Control.Monad
import Control.Concurrent.MVar
import Control.Concurrent (threadDelay)
import Control.Concurrent.Async
import qualified Graphics.NanoVG.Simple as N
import qualified Graphics.NanoVG.Picture as N
import qualified NanoVG as NVG
import Data.IORef
main :: IO ()
main = do
dm <- newEmptyMVar
race_
(simulate dm)
(display dm)
simulate :: MVar [VisObj] -> IO ()
simulate dm = do
let gravity = Vect 0 (-100)
-- Create an empty space.
space <- spaceNew
spaceGravity space $= gravity
static <- get $ spaceStaticBody space
-- Add a static line segment shape for the ground.
-- We'll make it slightly tilted so the ball will roll off.
-- We attach it to a static body to tell Chipmunk it shouldn't be movable.
let (segA, segB) = (Vect (-20) (-5), Vect 20 (-25))
ground <- segmentShapeNew static segA segB 0
shapeElasticity ground $= 0.6
shapeFriction ground $= 1
spaceAddShape space ground
-- Now let's make a ball that falls onto the line and rolls off.
-- First we need to make a cpBody to hold the physical properties of the object.
-- These include the mass, position, velocity, angle, etc. of the object.
-- Then we attach collision shapes to the cpBody to give it a size and shape.
let radius = 5
let mass = 1
let mass100 = 100
-- The moment of inertia is like mass for rotation
-- Use the cpMomentFor*() functions to help you approximate it.
let moment = momentForCircle mass 0 radius (Vect 0 0)
let moment100 = momentForCircle mass100 0 radius (Vect 0 0)
-- The cpSpaceAdd*() functions return the thing that you are adding.
-- It's convenient to create and add an object in one line.
ballBody <- bodyNew mass moment
spaceAddBody space ballBody
-- Now we create the collision shape for the ball.
-- You can create multiple collision shapes that point to the same body.
-- They will all be attached to the body and move around to follow it.
ballShape <- circleShapeNew ballBody radius (Vect 0 0)
shapeFriction ballShape $= 0.9
shapeElasticity ballShape $= 1
spaceAddShape space ballShape
anotherBall <- bodyNew mass100 moment100
spaceAddBody space anotherBall
anotherBallShape <- circleShapeNew anotherBall radius (Vect 0 0)
shapeFriction anotherBallShape $= 0.9
shapeElasticity anotherBallShape $= 0.4
spaceAddShape space anotherBallShape
putMVar dm
[ mkStaticObj $ Segment segA segB
, mkBallBody ballBody radius
, mkBallBody anotherBall radius
]
void $ forever $ do
bodyPosition ballBody $= Vect (-15) 30
bodyPosition anotherBall $= Vect (-5) 75
-- need to reset ball velocity after previous iteration
bodyVelocity ballBody $= Vect 0 0
bodyAngularVelocity ballBody $= 0
bodyVelocity anotherBall $= Vect 0 0
bodyAngularVelocity anotherBall $= 0
-- Now that it's all set up, we simulate all the objects in the space by
-- stepping forward through time in small increments called steps.
-- It is *highly* recommended to use a fixed size time step.
let timeStep = 1/60
runFor 3 timeStep $ \time -> do
pos <- get $ bodyPosition ballBody
vel <- get $ bodyVelocity ballBody
printf "Time is %4.2f. ballBody is at (%6.2f, %6.2f), it's velocity is (%6.2f, %6.2f).\n"
time (vX pos) (vY pos) (vX vel) (vY vel)
threadDelay $ round $ timeStep * 1000 * 1000
spaceStep space timeStep
shapeFree ballShape
bodyFree ballBody
shapeFree ground
spaceFree space
where
runFor time step inner = go time
where
go time'
| time' <= 0 = pure ()
| otherwise = inner (time - time') *> go (time' - step)
display :: MVar [VisObj] -> IO ()
display dm = do
d <- takeMVar dm
N.run 800 600 "Chiphunk" $
N.showFPS "Liberation Sans" $
N.loadFont "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf" "Liberation Sans" $
N.asWindow $
N.translateP 400 300 .
N.scaleP' (0, 0) 10 .
N.scalePy (0, 0) (-1) .
N.pictures <$>
sequence ((render <$>) . runVisObj <$> d)
where
render = \case
Segment (Vect ax ay) (Vect bx by) -> N.stroke (NVG.Color 1 1 1 1) $
N.line (realToFrac ax, realToFrac ay) (realToFrac bx, realToFrac by)
Ball (Vect x y) r a ->
let c = (realToFrac x, realToFrac y)
in N.stroke (NVG.Color 1 1 1 1) $
N.rotateS c (realToFrac a) $
N.shapes
[ N.circle c (realToFrac r)
, N.line c (realToFrac $ x - r / 2, realToFrac y)
]
data VisShape =
Segment
{ segEndpointA :: Vect
, segEndpointB :: Vect
}
| Ball
{ ballCenter :: Vect
, ballRadius :: Double
, ballAngle :: Double
}
deriving Show
newtype VisObj = VisObj
{ runVisObj :: IO VisShape
}
mkRefObj :: IORef VisShape -> VisObj
mkRefObj r = VisObj $ readIORef r
mkStaticObj :: VisShape -> VisObj
mkStaticObj = VisObj . pure
mkBallBody :: Body -> Double -> VisObj
mkBallBody b r = VisObj $ Ball <$> get (bodyPosition b)
<*> pure r
<*> get (bodyAngle b)