diff options
Diffstat (limited to 'Demos/Balls/Main.hs')
-rw-r--r-- | Demos/Balls/Main.hs | 176 |
1 files changed, 176 insertions, 0 deletions
diff --git a/Demos/Balls/Main.hs b/Demos/Balls/Main.hs new file mode 100644 index 0000000..d266d85 --- /dev/null +++ b/Demos/Balls/Main.hs | |||
@@ -0,0 +1,176 @@ | |||
1 | {-# LANGUAGE MultiParamTypeClasses #-} | ||
2 | --{-# LANGUAGE NoImplicitPrelude #-} | ||
3 | {-# LANGUAGE FlexibleContexts #-} | ||
4 | {-# LANGUAGE TypeSynonymInstances #-} | ||
5 | |||
6 | module Main where | ||
7 | |||
8 | import Spear.App | ||
9 | import Spear.Game | ||
10 | import Spear.Math.AABB | ||
11 | import qualified Spear.Math.Matrix3 as Matrix3 | ||
12 | import qualified Spear.Math.Matrix4 as Matrix4 | ||
13 | import Spear.Math.Spatial | ||
14 | import Spear.Math.Spatial2 | ||
15 | import Spear.Math.Vector | ||
16 | import Spear.Physics.Collision | ||
17 | --import Spear.Prelude | ||
18 | import Spear.Render.Core.Pipeline | ||
19 | import Spear.Render.Core.State | ||
20 | import Spear.Render.Immediate | ||
21 | import Spear.Sound.Sound | ||
22 | import Spear.Sound.State | ||
23 | import Spear.Window | ||
24 | |||
25 | import Control.Monad (when) | ||
26 | |||
27 | |||
28 | ballSize = 0.01 | ||
29 | numBalls = 1000 | ||
30 | |||
31 | data Ball = Ball | ||
32 | { ballPosition :: {-# UNPACK #-} !Vector2 | ||
33 | , ballVelocity :: {-# UNPACK #-} !Vector2 | ||
34 | } | ||
35 | |||
36 | instance Positional Ball Vector2 where | ||
37 | setPosition p ball = ball { ballPosition = p } | ||
38 | position = ballPosition | ||
39 | translate v ball = ball { ballPosition = v + ballPosition ball } | ||
40 | |||
41 | instance Bounded2 Ball where | ||
42 | boundingVolume ball = aabb2Volume $ translate (ballPosition ball) (AABB2 (-size) size) | ||
43 | where size = vec2 s s | ||
44 | s = ballSize / (2::Float) | ||
45 | |||
46 | data World = World | ||
47 | { viewProjection :: Matrix4.Matrix4 | ||
48 | , balls :: [Ball] | ||
49 | } | ||
50 | |||
51 | type GameState = AppState World | ||
52 | |||
53 | |||
54 | options = defaultAppOptions { title = "Balls" } | ||
55 | |||
56 | app = App options initGame endGame step render resize | ||
57 | |||
58 | |||
59 | main :: IO () | ||
60 | main = runApp app | ||
61 | |||
62 | initGame :: Game AppContext World | ||
63 | initGame = | ||
64 | let | ||
65 | world = zipWith Ball positions velocities | ||
66 | positions = (+vec2 0.5 0.5) . makePosition <$> numbers | ||
67 | makePosition i = radius * vec2 (sin (f*i)) (cos (f*i)) | ||
68 | velocities = makeVelocity <$> numbers | ||
69 | makeVelocity i = scale speed $ vec2 (sin (f*i)) (cos (f*i)) | ||
70 | numbers = [1..numBalls] | ||
71 | f = 2*pi / numBalls | ||
72 | radius = 0.05 | ||
73 | speed = 0.4 | ||
74 | in | ||
75 | return $ World Matrix4.id world | ||
76 | |||
77 | endGame :: Game GameState () | ||
78 | endGame = return () | ||
79 | |||
80 | |||
81 | step :: Elapsed -> Dt -> [InputEvent] -> Game GameState Bool | ||
82 | step elapsed dt inputEvents = do | ||
83 | modifyGameState $ \world -> world | ||
84 | { balls = moveBalls dt $ balls world | ||
85 | } | ||
86 | return (not $ exitRequested inputEvents) | ||
87 | |||
88 | exitRequested = elem (KeyDown KEY_ESC) | ||
89 | |||
90 | moveBalls :: Elapsed -> [Ball] -> [Ball] | ||
91 | moveBalls dt = (bounceBall dt . moveBall dt <$>) | ||
92 | |||
93 | moveBall :: Elapsed -> Ball -> Ball | ||
94 | moveBall dt ball = translate (scale (realToFrac dt) $ ballVelocity ball) ball | ||
95 | |||
96 | bounceBall :: Elapsed -> Ball -> Ball | ||
97 | bounceBall dt ball = | ||
98 | let | ||
99 | (AABB2Volume (AABB2 pmin pmax)) = boundingVolume ball | ||
100 | sideCollision = x pmin < 0 || x pmax > 1 | ||
101 | backCollision = y pmin < 0 || y pmax > 1 | ||
102 | flipX v@(Vector2 x y) = if sideCollision then vec2 (-x) y else v | ||
103 | flipY v@(Vector2 x y) = if backCollision then vec2 x (-y) else v | ||
104 | velocity = ballVelocity ball | ||
105 | velocity' | ||
106 | = flipX | ||
107 | . flipY | ||
108 | $ velocity | ||
109 | collision = velocity' /= velocity | ||
110 | -- Apply offset when collision occurs to avoid sticky collisions. | ||
111 | delta = if collision then 1 else 0 | ||
112 | dt' = realToFrac dt | ||
113 | in | ||
114 | ball | ||
115 | { ballPosition = ballPosition ball + scale (delta * dt') velocity' | ||
116 | , ballVelocity = velocity' | ||
117 | } | ||
118 | |||
119 | |||
120 | render :: Game GameState () | ||
121 | render = do | ||
122 | gameState <- getGameState | ||
123 | siblingGame $ do | ||
124 | immStart | ||
125 | immSetViewProjectionMatrix (viewProjection gameState) | ||
126 | -- Clear the background to a different colour than the playable area to make | ||
127 | -- the latter distinguishable. | ||
128 | setClearColour (0.2, 0.2, 0.2, 0.0) | ||
129 | clearBuffers [ColourBuffer] | ||
130 | render' $ balls gameState | ||
131 | immEnd | ||
132 | |||
133 | render' :: [Ball] -> Game ImmRenderState () | ||
134 | render' balls = do | ||
135 | immLoadIdentity | ||
136 | renderBackground | ||
137 | -- Draw objects. | ||
138 | immSetColour (vec4 1.0 1.0 1.0 1.0) | ||
139 | mapM_ renderBall balls | ||
140 | |||
141 | renderBackground :: Game ImmRenderState () | ||
142 | renderBackground = | ||
143 | let pmin = 0 :: Float | ||
144 | pmax = 1 :: Float | ||
145 | in do | ||
146 | immSetColour (vec4 0.0 0.25 0.41 1.0) | ||
147 | immDrawQuads2d [ | ||
148 | (vec2 pmin pmin | ||
149 | ,vec2 pmax pmin | ||
150 | ,vec2 pmax pmax | ||
151 | ,vec2 pmin pmax)] | ||
152 | |||
153 | renderBall :: Ball -> Game ImmRenderState () | ||
154 | renderBall ball = | ||
155 | let (AABB2Volume (AABB2 (Vector2 xmin ymin) (Vector2 xmax ymax))) = boundingVolume ball | ||
156 | in | ||
157 | immDrawQuads2d [ | ||
158 | (vec2 xmin ymin | ||
159 | ,vec2 xmax ymin | ||
160 | ,vec2 xmax ymax | ||
161 | ,vec2 xmin ymax)] | ||
162 | |||
163 | |||
164 | resize :: WindowEvent -> Game GameState () | ||
165 | resize (ResizeEvent w h) = | ||
166 | let r = fromIntegral w / fromIntegral h | ||
167 | pad = if r > 1 then (r-1) / 2 else (1/r - 1) / 2 | ||
168 | left = if r > 1 then -pad else 0 | ||
169 | right = if r > 1 then 1 + pad else 1 | ||
170 | bottom = if r > 1 then 0 else -pad | ||
171 | top = if r > 1 then 1 else 1 + pad | ||
172 | in do | ||
173 | setViewport 0 0 w h | ||
174 | modifyGameState $ \pong -> pong { | ||
175 | viewProjection = Matrix4.ortho left right bottom top (-1) 1 | ||
176 | } | ||