gloss-examples (empty) → 1.0.0.0
raw patch · 25 files changed
+1532/−0 lines, 25 filesdep +basedep +containersdep +ghc-primsetup-changed
Dependencies added: base, containers, ghc-prim, gloss, random
Files
- Clock/Main.hs +92/−0
- Easy/Main.hs +3/−0
- Eden/Cell.hs +38/−0
- Eden/Community.hs +55/−0
- Eden/Main.hs +21/−0
- Eden/World.hs +47/−0
- Flake/Main.hs +50/−0
- Hello/Main.hs +16/−0
- LICENSE +13/−0
- Lifespan/Cell.hs +36/−0
- Lifespan/Community.hs +58/−0
- Lifespan/Main.hs +22/−0
- Lifespan/World.hs +43/−0
- Setup.hs +2/−0
- Styrene/Actor.hs +73/−0
- Styrene/Advance.hs +129/−0
- Styrene/Collide.hs +166/−0
- Styrene/Config.hs +46/−0
- Styrene/Contact.hs +132/−0
- Styrene/Main.hs +114/−0
- Styrene/QuadTree.hs +90/−0
- Styrene/World.hs +90/−0
- Tree/Main.hs +58/−0
- Zen/Main.hs +68/−0
- gloss-examples.cabal +70/−0
+ Clock/Main.hs view
@@ -0,0 +1,92 @@++-- A fractal consisting of circles and lines which looks a bit like+-- the workings of a clock.+import Graphics.Gloss++main+ = animateInWindow+ "Clock"+ (600, 600)+ (20, 20)+ black+ frame+++-- Build the fractal, scale it so it fits in the window+-- and rotate the whole thing as time moves on.+frame :: Float -> Picture+frame time+ = Color white+ $ Scale 120 120+ $ Rotate (time * 2*pi)+ $ clockFractal 5 time+ ++-- The basic fractal consists of three circles offset from the origin+-- as follows.+--+-- 1+-- |+-- .+-- / \+-- 2 3+--+-- The direction of rotation switches as n increases.+-- Components at higher iterations also spin faster.+--+clockFractal :: Int -> Float -> Picture+clockFractal 0 s = Blank+clockFractal n s = Pictures [circ1, circ2, circ3, lines]+ where+ -- y offset from origin to center of circle 1.+ a = 1 / sin (2 * pi / 6)++ -- x offset from origin to center of circles 2 and 3.+ b = a * cos (2 * pi / 6)++ nf = fromIntegral n+ rot = if n `mod` 2 == 0+ then 50 * s * (log (1 + nf))+ else (-50 * s * (log (1 + nf)))++ -- each element contains a copy of the (n-1) iteration contained+ -- within a larger circle, and some text showing the time since + -- the animation started.+ --+ circNm1 + = Pictures+ [ circle 1 50+ , Scale (a/2.5) (a/2.5) $ clockFractal (n-1) s+ , if n > 2+ then Color cyan + $ Translate (-0.15) 1+ $ Scale 0.001 0.001 + $ Text (show s) + else Blank+ ]++ circ1 = Translate 0 a $ Rotate rot circNm1+ circ2 = Translate 1 (-b) $ Rotate (-rot) circNm1+ circ3 = Translate (-1) (-b) $ Rotate rot circNm1+ + -- join each iteration to the origin with some lines.+ lines + = Pictures+ [ Line [(0, 0), ( 0, a)]+ , Line [(0, 0), ( 1, -b)]+ , Line [(0, 0), (-1, -b)] ]+++-- Make a circle of radius r consisting of n lines.+circle :: Float -> Float -> Picture+circle r n+ = Scale r r+ $ Line (circlePoints n)+ + +-- A list of n points spaced equally around the unit circle.+circlePoints :: Float -> [(Float, Float)]+circlePoints n+ = map (\d -> (cos d, sin d))+ [0, 2*pi / n .. 2*pi]+
+ Easy/Main.hs view
@@ -0,0 +1,3 @@++import Graphics.Gloss+main = displayInWindow "My Window" (200, 200) (10, 10) white (Circle 80)
+ Eden/Cell.hs view
@@ -0,0 +1,38 @@+module Cell where++import Graphics.Gloss++data Cell + = Cell Point -- centre+ Float -- radius+ Int + deriving Show++-- Produce a new cell of a certain relative radius at a certain angle.+-- The factor argument is in the range [0..1] so spawned cells are+-- smaller than their parent.+-- The check whether it fits in the community is elsewhere.+offspring :: Cell -> Float -> Float -> Cell+offspring (Cell (x,y) r gen) alpha factor + = Cell (x + (childR+r) * cos alpha, y + (childR+r) * sin alpha) + childR + (gen + 1)++ where childR = factor * r++-- Do two cells overlap? +-- Used to decide if newly spawned cells can join the community.+overlap :: Cell -> Cell -> Bool+overlap (Cell (x1,y1) r1 _) (Cell (x2,y2) r2 _) + = centreDist < (r1 + r2) * 0.999+ where centreDist = sqrt(xdiff*xdiff + ydiff*ydiff)+ xdiff = x1 - x2+ ydiff = y1 - y2++render :: Cell -> Picture+render (Cell (x,y) r gen) + = let z = fromIntegral gen * 0.1+ color = makeColor 0.0 z 0.5 1.0+ in Color color+ $ Translate x y+ $ Circle r
+ Eden/Community.hs view
@@ -0,0 +1,55 @@+module Community where++import Cell+import Graphics.Gloss++type Community = [Cell]++-- does a (newly spawned) cell fit in the community?+-- that is, does it overlap with any others?+fits :: Cell -> Community -> Bool+fits cell cells + = not $ any (overlap cell) cells++-- For each member of a community, produce one offspring+-- The lists of Floats are the (random) parameters that determine size+-- and location of each offspring.+spawn :: Community -> [Float] -> [Float] -> [Cell]+spawn = zipWith3 offspring++-- Given a collection of cells (one spawned by each member of the+-- community) check if it fits, and if so add it to the community.+-- That check must include new cells that have been added to the+-- community in this process.+survive :: [Cell] -> Community -> Community+survive [] comm = comm+survive (cell:cells) comm+ | fits cell comm = survive cells (cell:comm)+ | otherwise = survive cells comm++-- The next generation of a community+generation :: Community -> [Float] -> [Float] -> Community+generation comm angles scales + = survive (spawn comm angles scales) comm++render :: Community -> Picture+render comm + = Pictures $ map Cell.render comm++initial :: Community+initial = [Cell (0,0) 50 0]+++-- thread the random lists for testing outside IO()+--+life :: Community -> [Float] -> [Float] -> (Community, [Float], [Float])+life comm randomAngles randomScales =+ (generation comm angles scales, randomAngles', randomScales')+ where population = length comm+ (angles, randomAngles') = splitAt population randomAngles+ (scales, randomScales') = splitAt population randomScales++evolution :: Community -> [Float] -> [Float] -> [Community]+evolution comm randomAngles randomScales = comm1 : comms+ where (comm1, ras, rss) = life comm randomAngles randomScales+ comms = evolution comm1 ras rss
+ Eden/Main.hs view
@@ -0,0 +1,21 @@++-- Adapted from ANUPlot version by Clem Baker-Finch+module Main where+import World+import Graphics.Gloss+import System.Random++-- varying prng sequence+main + = do gen <- getStdGen+ simulateInWindow+ "Eden" -- window name+ (800, 600) -- window size+ (10, 10) -- window position+ (greyN 0.1) -- background color+ 2 -- number of steps per second+ (genesis' gen) -- initial world+ render -- function to convert world to a Picture+ evolve -- function to step the world one iteration++
+ Eden/World.hs view
@@ -0,0 +1,47 @@+module World where+++import Graphics.Gloss+import System.Random+import Community+import Cell++maxSteps = 30++-- The World consists of a Community and a random number generator.+-- (The RNG is a model of chaos or hand-of-god.)+data World + = World Community StdGen Int+ deriving (Show)++-- The initial world+genesis :: World+genesis + = World [Cell (0,0) 30 0] (mkStdGen 1023) 0++-- Seeding the prng means every run is identical.+-- To get different runs, need to use gen <- getStdGen in main :: IO()+-- and pass gen in as an argument. Edit Main.hs accordingly.+genesis' :: StdGen -> World+genesis' gen + = World [Cell (0,0) 30 0] gen 0+++-- Consume some random numbers to advance the simulation+evolve :: ViewPort -> Float -> World -> World+evolve vp step world@(World comm gen steps) + | steps < maxSteps + = let (genThis, genNext) = split gen+ (genA, genS) = split genThis+ angles = randomRs (0.0, 2*pi) genA+ scales = randomRs (0.7, 0.9) genS+ in World (generation comm angles scales) genNext (steps + 1)++ | otherwise+ = world++-- Converting the world to a picture is just converting the community component+render :: World -> Picture+render (World comm gen steps) + = Color (makeColor 0.3 0.3 0.6 1.0)+ $ Community.render comm
+ Flake/Main.hs view
@@ -0,0 +1,50 @@++-- | Snowflake Fractal.+-- Based on ANUPlot code by Clem Baker-Finch.+--+import Graphics.Gloss++main = displayInWindow+ "Snowflake"+ (500, 500)+ (20, 20)+ black+ (picture 3)+++-- Fix a starting edge length of 360+edge = 360 :: Float+++-- Move the fractal into the center of the window and colour it nicely+picture :: Int -> Picture+picture degree + = Color aquamarine+ $ Translate (-edge/2) (-edge * sqrt 3/6)+ $ snowflake degree+ ++-- The fractal function+side :: Int -> Picture+side 0 = Line [(0, 0), (edge, 0)]+side n + = let newSide = Scale (1/3) (1/3) + $ side (n-1)+ in Pictures+ [ newSide+ , Translate (edge/3) 0 $ Rotate 60 newSide + , Translate (edge/2) (-(edge * sqrt 3)/6) $ Rotate (-60) newSide + , Translate (2 * edge/3) 0 $ newSide ]+++-- Put 3 together to form the snowflake+snowflake :: Int -> Picture+snowflake n + = let oneSide = side n+ in Pictures+ [ oneSide + , Translate edge 0 $ Rotate (-120) $ oneSide+ , Translate (edge/2) (edge * sqrt 3/2) $ Rotate 120 $ oneSide]+++
+ Hello/Main.hs view
@@ -0,0 +1,16 @@++-- | Display "Hello World" in a window.+--+import Graphics.Gloss++main = displayInWindow + "Hello World" -- window title+ (400, 150) -- window size+ (10, 10) -- window position+ white -- background color+ picture -- picture to display++picture + = Translate (-170) (-20) -- shift the text to the middle of the window+ $ Scale 0.5 0.5 -- display it half the original size+ $ Text "Hello World" -- text to display
+ LICENSE view
@@ -0,0 +1,13 @@+Copyright (c) 2010 Benjamin Lippmeier ++ Permission is hereby granted, free of charge, to any person+ obtaining a copy of this software and associated documentation+ files (the "Software"), to deal in the Software without+ restriction, including without limitation the rights to use,+ copy, modify, merge, publish, distribute, sublicense, and/or sell+ copies of the Software, and to permit persons to whom the+ Software is furnished to do so, subject to the following+ condition:++ The above copyright notice and this permission notice shall be+ included in all copies or substantial portions of the Software.
+ Lifespan/Cell.hs view
@@ -0,0 +1,36 @@+module Cell where++import Graphics.Gloss++data Cell = Cell Point -- centre+ Float -- radius+ Int -- remaining lifetime+ deriving Show++-- Produce a new cell of a certain relative radius at a certain angle.+-- The factor argument is in the range [0..1] so spawned cells are+-- smaller than their parent.+-- The check whether it fits in the community is elsewhere.+offspring :: Cell -> Float -> Float -> Int -> Cell+offspring (Cell (x,y) r _) alpha factor lifespan =+ Cell (x + (childR+r) * cos alpha, y + (childR+r) * sin alpha)+ childR + lifespan+ where childR = factor * r++-- Do two cells overlap? +-- Used to decide if newly spawned cells can join the community.+overlap :: Cell -> Cell -> Bool+overlap (Cell (x1,y1) r1 _) (Cell (x2,y2) r2 _) = centreDist < (r1 + r2) *0.999+ where centreDist = sqrt(xdiff*xdiff + ydiff*ydiff)+ xdiff = x1 - x2+ ydiff = y1 - y2++-- thickness of circle is determined by lifespan+render :: Cell -> Picture+render (Cell (x,y) r life) + = Color (makeColor 0.6 z 0.6 1.0)+ $ Translate x y+ $ ThickCircle (r - thickness / 2) thickness+ where z = fromIntegral life * 0.12+ thickness = fromIntegral life
+ Lifespan/Community.hs view
@@ -0,0 +1,58 @@+module Community where++import Cell+import Graphics.Gloss++type Community = [Cell]++-- does a (newly spawned) cell fit in the community?+-- that is, does it overlap with any others?+fits :: Cell -> Community -> Bool+fits cell cells = not $ any (overlap cell) cells++-- For each member of a community, produce one offspring+-- The lists of Floats are the (random) parameters that determine size++-- and location of each offspring.+spawn :: Community -> [Float] -> [Float] -> [Int] -> [Cell]+spawn = zipWith4 offspring++zipWith4 :: (a -> b -> c -> d -> e) -> [a] -> [b] -> [c] -> [d] -> [e]+zipWith4 f [] _ _ _ = []+zipWith4 f _ [] _ _ = []+zipWith4 f _ _ [] _ = []+zipWith4 f _ _ _ [] = []+zipWith4 f (b:bs) (c:cs) (d:ds) (e:es) =+ f b c d e : zipWith4 f bs cs ds es+++-- Given a collection of cells (one spawned by each member of the+-- community) check if it fits, and if so add it to the community.+-- That check must include new cells that have been added to the+-- community in this process.+survive :: [Cell] -> Community -> Community+survive [] comm = comm+survive (cell:cells) comm+ | fits cell comm = survive cells (cell:comm)+ | otherwise = survive cells comm++age :: Community -> Community+age [] = []+age (Cell c r 0 : cells) = age cells+age (Cell c r life : cells) = Cell c r (life-1) : age cells+++-- The next generation of a community+generation :: Community -> [Float] -> [Float] -> Community+generation comm angles scales =+ survive (spawn comm angles scales (repeat 5)) (age comm)++render :: Community -> Picture+render comm + = Pictures + $ map Cell.render comm++initial :: Community+initial = [Cell (0,0) 50 5]++
+ Lifespan/Main.hs view
@@ -0,0 +1,22 @@++-- Adapted from ANUPlot version by Clem Baker-Finch+module Main where+import World+import Graphics.Gloss+import System.Random++-- varying prng sequence+main + = do gen <- getStdGen+ simulateInWindow+ "Lifespan" -- window name+ (800, 600) -- window size+ (10, 10) -- window position+ (greyN 0.1) -- background color+ 2 -- number of steps per second+ (genesis' gen) -- initial world+ render -- function to convert world to a Picture+ evolve -- function to step the world one iteration+++
+ Lifespan/World.hs view
@@ -0,0 +1,43 @@+module World where++import Graphics.Gloss+import System.Random+import Community+import Cell++stepsMax = 20++-- The World consists of a Community and a random number generator.+-- (The RNG is a model of chaos or hand-of-god.)+data World + = World Community StdGen Int+ deriving (Show)++-- The initial world+genesis :: World+genesis + = World [Cell (0,0) 50 5] (mkStdGen 1023) 0++-- Seeding the prng means every run is identical.+-- To get different runs, need to use gen <- getStdGen in main :: IO()+-- and pass gen in as an argument. Edit Main.hs accordingly.+genesis' :: StdGen -> World+genesis' gen + = World [Cell (0,0) 50 5] gen 0++-- Consume some random numbers to advance the simulation+evolve :: ViewPort -> Float -> World -> World+evolve _ _ world@(World comm gen step) + | step > stepsMax = world+ | otherwise+ = World (generation comm angles scales) genNext (step + 1)+ where (genThis, genNext) = split gen+ (genA, genS) = split genThis+ angles = randomRs (0.0, 2*pi) genA+ scales = randomRs (0.7, 0.9) genS++-- Converting the world to a picture is just converting the community component+render :: World -> Picture+render (World comm gen _) + = Color (makeColor 0.3 0.3 0.6 1.0)+ $ Community.render comm
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ Styrene/Actor.hs view
@@ -0,0 +1,73 @@++module Actor where++-- | 2D position on the screen.+type Position = (Float, Float)++-- | Force and velocity vectors.+type Force = (Float, Float)+type Velocity = (Float, Float)++-- | Time in seconds+type Time = Float++-- | Radius of a bead+type Radius = Float++-- | Each actor has its own unique index.+type Index = Int++-- | The actors in the world.+data Actor+ = Wall !Index -- ^ unique index of this actor+ !Position -- ^ wall starting point+ !Position -- ^ wall ending point++ | Bead !Index -- ^ unique index of this actor + !Int -- ^ whether the bead is stuck+ !Radius -- ^ radius of bead+ !Position -- ^ position of bead+ !Velocity -- ^ velocity of bead++ deriving Show++-- | Equality and ordering of actors will consider their index only.+-- We need Ord so we can put them in Maps and Sets.+instance Eq Actor where+ a1 == a2 = actorIx a1 == actorIx a2+ +instance Ord Actor where+ compare a1 a2 = compare (actorIx a1) (actorIx a2)++-- | Check whether an actor is a bead.+isBead :: Actor -> Bool+isBead (Bead _ _ _ _ _) = True+isBead _ = False+++-- | Check whether an actor is a wall.+isWall :: Actor -> Bool+isWall (Wall _ _ _) = True+isWall _ = False+++-- | Take the index of an actor+actorIx :: Actor -> Index+actorIx actor+ = case actor of+ Wall ix _ _ -> ix+ Bead ix _ _ _ _ -> ix+++-- | Set the index of an actor+actorSetIndex :: Actor -> Index -> Actor+actorSetIndex actor ix+ = case actor of+ Bead _ m r pos vel -> Bead ix m r pos vel + Wall _ p1 p2 -> Wall ix p1 p2+++-- | Set whether a bead is stuck+actorSetMode :: Int -> Actor -> Actor+actorSetMode m (Bead ix _ r p v)+ = Bead ix m r p v
+ Styrene/Advance.hs view
@@ -0,0 +1,129 @@+{-# LANGUAGE PatternGuards #-}++-- | Advance the world to the next time step.+module Advance where+import World+import Contact+import QuadTree+import Collide+import Actor+import Config++import Graphics.Gloss.Geometry+import Graphics.Gloss.ViewPort+import Graphics.Gloss.Picture (Point)++import Data.List+import qualified Data.Map as Map+import qualified Data.Set as Set+import Data.Set (Set)+import Data.Map (Map)+++-- Advance -------------------------------------------------------------------------------------------++-- | Advance all the actors in this world by a certain time.+advanceWorld + :: ViewPort -- ^ current viewport+ -> Time -- ^ time to advance them for.+ -> World -- ^ the world to advance.+ -> World -- ^ the new world.++advanceWorld viewport time (World actors tree)+ = let + rot = viewPortRotate viewport+ force = rotateV (degToRad $ negate rot) (0, negate gravityCoeff)++ -- move all the actors + actors_moved = Map.map (moveActor_free time force) actors+ + -- find contacts in the world+ (contacts, tree') + = findContacts (World actors_moved tree)++ -- apply contacts to each pair of actors+ actors_bounced + = Set.fold + (applyContact time force) + actors_moved+ contacts++ in World actors_bounced tree'+++-- Move two actors which are known to be in contact.+applyContact + :: Time -- ^ time step+ -> Force -- ^ ambient force on the actors+ -> (Index, Index) -- ^ indicies of the the two actors in contact+ -> Map Index Actor -- ^ the old world+ -> Map Index Actor -- ^ the new world++applyContact time force (ix1, ix2) actors+ = let -- use the indicies to lookup the data for each actor from the map+ Just a1 = Map.lookup ix1 actors+ Just a2 = Map.lookup ix2 actors+ + resultActors+ -- handle a collision between bead and a wall+ | Bead _ _ r1 p1 v1 <- a1+ , Wall{} <- a2+ = let a1' = collideBeadWall a1 a2+ in Map.insert ix1 a1' actors+ + -- handle a collision between two beads+ | Bead ix1 m1 r1 p1 v1 <- a1+ , Bead ix2 m2 r2 p2 v2 <- a2+ = let + (a1', a2')+ -- if one of the beads is stuck then do a safer, static collision.+ -- with this method the beads don't transfer energy into each other+ -- so there is less of a chance of lots of beads being crushed together+ -- if there are many in the same place.+ | m1 >= beadStuckCount || m2 >= beadStuckCount+ = let a1' = collideBeadBead_static a1 a2+ a2' = collideBeadBead_static a2 a1+ in (a1', a2')++ -- otherwise do the real elastic collision+ -- this is much more realistic.+ | otherwise+ = collideBeadBead_elastic a1 a2++ -- write the new data for the actors back into the map+ in Map.insert ix1 a1'+ $ Map.insert ix2 a2' actors+ + in resultActors + ++-- | Move a bead which isn't in contact with anything else.+moveActor_free + :: Time -- ^ time to move it for+ -> Force -- ^ ambient force on the actor during this time+ -> Actor -- ^ the bead to move+ -> Actor -- ^ the new bead++moveActor_free time force actor+ -- move a bead+ | Bead ix stuck radius pos vel <- actor+ = let -- assume all beads have the same mass.+ beadMass = 1+ + -- calculate the new position and velocity of the bead.+ pos' = (pos + time `mulSV` vel)+ vel' = (vel + (time / beadMass) `mulSV` force)++ -- if the bead is travelling slowly then set it as being stuck.+ stuck' + | magV vel' < 20+ = min beadStuckCount (stuck + 1)++ | otherwise + = max 0 (stuck - 2)++ in Bead ix stuck' radius pos' vel'++ -- walls don't move+ | Wall{} <- actor+ = actor
+ Styrene/Collide.hs view
@@ -0,0 +1,166 @@+-- | Physics for bead bouncing.+module Collide where+import World+import Actor+import Graphics.Gloss.Picture (Point)+import Graphics.Gloss.Geometry++-- Config -----------------------------------------------------------------------------------------+-- How bouncy the beads are+-- at 0.2 and they look like melting plastic.+-- at 0.8 and they look like bouncy rubber balls.+-- at > 1 and they gain energy with each bounce and escape the box.+--+beadBeadLoss = 0.95+beadWallLoss = 0.8+++-- | Move a bead which is in contact with a wall.+collideBeadWall+ :: Actor -- ^ the bead + -> Actor -- ^ the wall that bead is in contact with+ -> Actor -- ^ the new bead++collideBeadWall+ bead@(Bead ix _ radius pBead vIn@(velX, velY))+ wall@(Wall _ pWall1 pWall2)++ = let -- Take the collision point as being the point on the wall which is + -- closest to the bead's center.+ pCollision = closestPointOnLine pWall1 pWall2 pBead+ + -- then do a static, non energy transfering collision.+ in collideBeadPoint_static + bead + pCollision+ beadWallLoss+++-- | Move two beads which have bounced into each other.+collideBeadBead_elastic+ :: Actor -> Actor+ -> (Actor, Actor)++collideBeadBead_elastic+ bead1@(Bead ix1 mode1 r1 p1 v1) + bead2@(Bead ix2 mode2 r2 p2 v2)++ = let mass1 = 1+ mass2 = 1++ -- the axis of collision (towards p2)+ vCollision@(cX, cY) = normaliseV (p2 - p1)+ vCollisionR = (cY, -cX)+ + -- the velocity component of each bead along the axis of collision+ s1 = dotV v1 vCollision+ s2 = dotV v2 vCollision++ -- work out new velocities along the collision+ s1' = (s1 * (mass1 - mass2) + 2 * mass2 * s2) / (mass1 + mass2)+ s2' = (s2 * (mass2 - mass1) + 2 * mass1 * s1) / (mass1 + mass2)+ + -- the velocity components at right angles to the collision+ -- there is no friction in the collision so these don't change+ k1 = dotV v1 vCollisionR+ k2 = dotV v2 vCollisionR+ + -- new bead velocities+ v1' = mulSV s1' vCollision + mulSV k1 vCollisionR+ v2' = mulSV s2' vCollision + mulSV k2 vCollisionR++ v1_slow = mulSV beadBeadLoss v1'+ v2_slow = mulSV beadBeadLoss v2'++ -- work out the point of collision+ u1 = r1 / (r1 + r2)+ u2 = r2 / (r1 + r2)++ pCollision + = p1 + mulSV u1 (p2 - p1)++ -- place the beads just next to each other so they are no longer overlapping.+ p1' = pCollision - (r1 + 0.001) `mulSV` vCollision+ p2' = pCollision + (r2 + 0.001) `mulSV` vCollision++ bead1' = Bead ix1 mode1 r1 p1' v1_slow+ bead2' = Bead ix2 mode2 r2 p2' v2_slow++ in (bead1', bead2')+++collideBeadBead_static+ :: Actor -> Actor + -> Actor+ +collideBeadBead_static+ bead1@(Bead ix1 _ radius1 pBead1 _)+ bead2@(Bead ix2 _ radius2 pBead2 _)++ = let -- Take the collision point as being between the center's of the two beads. + -- For beads which have the same radius the collision point is half way between+ -- their centers and u == 0.5+ u = radius1 / (radius1 + radius2)+ pCollision = pBead1 + mulSV u (pBead2 - pBead1)+ + bead1' = collideBeadPoint_static+ bead1+ pCollision+ beadBeadLoss+ in bead1'+++-- | Move a bead which has collided with something.+collideBeadPoint_static+ :: Actor -- ^ the bead which collided with something+ -> Point -- ^ the point of collision (should be near the bead's surface)+ -> Float -- ^ velocity scaling factor (how much to slow the bead down after the collision)+ -> Actor++collideBeadPoint_static+ bead@(Bead ix mode radius pBead vIn) + pCollision+ velLoss+ = let+ -- take a normal vector from the wall to the bead.+ -- this vector is at a right angle to the wall.+ vNormal = normaliseV (pBead - pCollision)+ + -- the bead at pBead is overlapping with what it collided with, but we don't want that.+ -- place the bead so it's surface is just next to the point of collision.+ pBead_new = pCollision + (radius + 0.01) `mulSV` vNormal++ -- work out the angle of incidence for the bounce.+ -- this is the angle between the surface normal and+ -- the direction of travel for the bead.+ aInc = angleVV vNormal (negate vIn)++ -- aInc2 is the angle between the wall /surface/ and+ -- the direction of travel.+ aInc2 = (pi / 2) - aInc++ -- take the determinant between the surface normal and the direction of travel.+ -- This will tell us what direction the bead hit the wall. + -- The diagram shows the sign of the determinant for the four possiblities.+ --+ -- \ +ve -ve /+ -- \ /+ -- \/ \/+ -- pWall1 ---------- pWall2 pWall1 ---------- pWall2+ -- /\ /\+ -- / \+ -- / -ve +ve \+ --+ determinant = detV vIn vNormal++ -- Use the determinant to rotate the bead's velocity vector for the bounce.+ vOut + | determinant > 0 = rotateV (2 * aInc2) vIn+ | otherwise = rotateV (negate (2 * aInc2)) vIn++ -- Slow down the bead when it hits the wall+ vSlow = velLoss `mulSV` vOut++ bead1_new = Bead ix mode radius pBead_new vSlow++ in bead1_new
+ Styrene/Config.hs view
@@ -0,0 +1,46 @@++module Config where+import Graphics.Gloss++-- Number of simulation steps per second of time.+simResolution :: Int+simResolution = 300++-- How strongly the beads are pulled down to the bottom of the screen.+-- If this is too high wrt the simResoution then the simulation+-- will be unstable and beads will escape the box.+gravityCoeff :: Float+gravityCoeff = 300++-- Whether to draw velocity vectors on beads.+showBeadVelocity = False++-- Colors of things.+beadColor = makeColor 0.5 0.5 1.0 1.0+beadOutlineColor = makeColor 1.0 1.0 1.0 1.0+nodeColor = makeColor 0.2 0.8 0.2 0.1+leafColor = makeColor 0.8 0.2 0.2 0.1++-- The maximum depth of the quad tree.+treeMaxDepth :: Int+treeMaxDepth = 4++-- Size of quadtree. Should be > boxSize.+treeSize :: Float+treeSize = 300++-- Size of bead box.+boxSize :: Float+boxSize = 280++-- Bead setup.+beadRadius, beadSpace, beadCountX, beadCountY, beadBoxSize :: Float++beadRadius = 5+beadSpace = 1+beadBoxSize = 2 * beadRadius + beadSpace+beadCountX = 20+beadCountY = 10++beadStuckCount :: Int+beadStuckCount = 20
+ Styrene/Contact.hs view
@@ -0,0 +1,132 @@+{-# OPTIONS -fglasgow-exts #-}++-- | Find actors in the world that are in contact with each other.+module Contact where+import World+import QuadTree+import Actor+import Graphics.Gloss.Picture (Point)+import Graphics.Gloss.Geometry.Line+import Data.Maybe+import Data.List+import GHC.Exts+import GHC.Prim+import Data.Map (Map)+import Data.Set (Set)+import qualified Data.Set as Set+import qualified Data.Map as Map+++-- Find all pairs of actors in the world that are in contact with each other.+findContacts + :: World + -> ( Set (Index, Index) -- ^ a set of all pairs of actors that are in contact.+ , QuadTree Actor) -- ^ also return the quadtree so we can draw it in the window.+ +findContacts (World actors _)+ = let + -- the initial tree has no actors in it and has a+ -- size of 300 (with is half the width of the box).+ treeInit = treeZero 300++ -- insert all the actors into the quadtree.+ tree' = Map.fold insertActor treeInit actors++ -- the potential contacts are lists of actors+ -- that _might_ be in contact.+ potentialContacts+ = treeElems tree'++ -- filter the lists of potential contacts to determine the actors+ -- which are _actually_ in contact.+ contactSet = makeContacts potentialContacts+ + in (contactSet, tree')+ ++-- | Make add all these test pairs to a map+-- normalise so the actor with the lowest ix is first in the pair.++makeContacts :: [[Actor]] -> Set (Index, Index)+makeContacts contactLists+ = makeContacts' Set.empty contactLists ++makeContacts' acc xx+ = case xx of+ -- no more potentials to add, return the current contact set+ [] -> acc++ -- add pairs of actors that are actually in contact to the contact set+ (list : lists)+ -> makeContacts' (makeTests acc list) lists+ +makeTests acc [] = acc+makeTests acc (x:xs)+ = makeTests (makeTests1 acc x xs) xs+ +makeTests1 acc a1 [] = acc+makeTests1 acc a1 (a2 : as)+ | inContact a1 a2+ = let k1 = actorIx a1+ k2 = actorIx a2+ contact = (min k1 k2, max k1 k2)+ acc' = Set.insert contact acc+ in makeTests1 acc' a1 as+ + | otherwise+ = makeTests1 acc a1 as+ ++-- See if these two actors are in contact+inContact :: Actor -> Actor -> Bool+inContact a1 a2+ | isBead a1 && isWall a2 = inContact_beadWall a1 a2+ | isWall a1 && isBead a2 = inContact_beadWall a2 a1+ | isBead a1 && isBead a2 = inContact_beadBead a1 a2+ | otherwise = False+++-- | Check whether a bead is in contact with a wall.+inContact_beadWall :: Actor -> Actor -> Bool+inContact_beadWall + bead@(Bead ix mode radius pBead _) + wall@(Wall _ pWall1 pWall2)++ = let -- work out the point on the infinite line between pWall1 and pWall2+ -- which is closest to the bead.+ pClosest = closestPointOnLine pWall1 pWall2 pBead++ -- the distance between the bead center and pClosest + -- needs to be less than the bead radius for them to touch.+ !(F# radius#) = radius+ closeEnough = distancePP_contact pBead pClosest `ltFloat#` radius#++ -- uParam gives where pClosest is relative to the endponts of the wall+ uParam = closestPointOnLine_param pWall1 pWall2 pBead++ -- pClosest needs to lie on the line segment between pWal1 and pWall2+ inSegment = uParam >= 0 && uParam <= 1++ in closeEnough && inSegment+++-- | Check whether a bead is in concat with another bead.+inContact_beadBead :: Actor -> Actor -> Bool+inContact_beadBead + bead1@(Bead ix1 _ radius1 pBead1 _) + bead2@(Bead ix2 _ radius2 pBead2 _)+ =let !dist# = distancePP_contact pBead1 pBead2+ !(F# rad) = radius1 + radius2+ in (dist# `ltFloat#` rad ) && (dist# `gtFloat#` 0.1#)+++-- | Return the distance between these two points.+{-# INLINE distancePP_contact #-}+distancePP_contact :: Point -> Point -> Float#+distancePP_contact (F# x1, F# y1) (F# x2, F# y2)+ = sqrtFloat# (xd2 `plusFloat#` yd2)+ where !xd = x2 `minusFloat#` x1+ !xd2 = xd `timesFloat#` xd++ !yd = y2 `minusFloat#` y1+ !yd2 = yd `timesFloat#` yd
+ Styrene/Main.hs view
@@ -0,0 +1,114 @@++import Actor+import Advance+import QuadTree+import Contact+import Collide+import World+import Config++import Graphics.Gloss+import Graphics.Gloss.Geometry+import Graphics.Gloss.Shapes ++import qualified Data.Map as Map+import Data.Map (Map)++main + = simulateInWindow + "Polystyrene - right-click-drag rotates"+ (600, 600) -- x and y size of window (in pixels).+ (10, 10) -- position of window+ black -- background color+ simResolution -- simulation resolution (number of steps to take for each second of time)+ worldInit -- the initial world.+ drawWorld -- a function to convert the world to a Picture.+ advanceWorld -- a function to advance the world to the next simulation step.++-- Draw --------------------------------------------------------------------------------------------++-- | Draw this world as a picture.+drawWorld :: World -> Picture+drawWorld (World actors tree)+ = let + -- split the list of actors into beads and walls.+ -- this lets us draw all the beads at once without having to keep changing + -- the current color (which is a bit of a performance improvement)+ (beads, walls) = splitActors $ Map.elems actors+ + picBeads = Color beadColor $ Pictures $ map drawActor beads+ picWalls = Pictures $ map drawActor walls+ picTree = drawQuadTree tree++ in Scale 0.8 0.8+ $ Pictures [picTree, picWalls, picBeads]+++-- | Split actors into beads and walls+splitActors :: [Actor] -> ([Actor], [Actor])+splitActors as+ = splitActors' [] [] as++splitActors' accBeads accWalls [] + = (accBeads, accWalls)++splitActors' accBeads accWalls (a : as) + = case a of+ Bead{} -> splitActors' (a : accBeads) accWalls as+ Wall{} -> splitActors' accBeads (a : accWalls) as+++-- | Draw an actor as a picture.+drawActor :: Actor -> Picture +drawActor actor + = case actor of+ Bead ix mode radius p@(posX, posY) v@(velX, velY)+ -> Translate posX posY $ Pictures [bead, vel]+ where bead = circleFilled radius 10+ vel = if showBeadVelocity+ then Color red $ Line [(0, 0), mulSV 0.1 v]+ else Blank+{- color+ | mode >= beadStuckCount = red+ | otherwise = beadColor+-} + Wall _ p1 p2+ -> Color (greyN 0.8) $ Line [p1, p2]+++-- | Draw a quadtree as a picture+drawQuadTree :: QuadTree a -> Picture+drawQuadTree tree + = case tree of+ QNode p size tTL tTR tBL tBR+ -> Pictures+ [ drawQuadTree tTL + , drawQuadTree tTR+ , drawQuadTree tBL+ , drawQuadTree tBR+ , nodeBox p size nodeColor ]++ QLeaf p size elems+ -> nodeBox p size leafColor+ + QNil (x0, y0) size+ -> Blank++nodeBox p@(x0, y0) size color+ = Color color+ $ Translate x0 y0+ $ rectangleWire (size*2) (size*2)+++-- Make a circle of radius r consisting of n lines.+circleFilled :: Float -> Float -> Picture+circleFilled r n+ = Scale r r+ $ Polygon (circlePoints n)+ + +-- A list of n points spaced equally around the unit circle.+circlePoints :: Float -> [(Float, Float)]+circlePoints n+ = map (\d -> (cos d, sin d))+ [0, 2*pi / n .. 2*pi]
+ Styrene/QuadTree.hs view
@@ -0,0 +1,90 @@++module QuadTree + ( QuadTree(..)+ , treeZero+ , treeInsert+ , treeElems )+where+import Graphics.Gloss.Picture (Point)++data QuadTree a+ -- Nil cells take up space in the world, but don't contain any elements.+ -- They can be at any depth in the tree.+ = QNil !Point -- cell center point + !Float -- cell size++ -- Leaf cells are the only ones that contain elements.+ -- They are always at the bottom of the tree.+ | QLeaf !Point -- cell center point + !Float -- cell size+ ![a] -- elements in this cell++ -- Node cells contain more sub-trees+ | QNode !Point -- cell center point+ !Float -- cell size+ !(QuadTree a) !(QuadTree a) -- NW NE+ !(QuadTree a) !(QuadTree a) -- SW SE+ + deriving (Eq, Show)+++-- Initial -----------------------------------------------------------------------------------------+treeZero size+ = QNil (0, 0) size++-- Quadrant ----------------------------------------------------------------------------------------++-- | Insert an element with a bounding box into the tree+treeInsert + :: Int -- ^ maximum depth to place a leaf+ -> Int -- ^ current depth+ -> Point -- ^ bottom left of bounding box of new element+ -> Point -- ^ top right of bounding box of new element+ -> a -- ^ element to insert into tree+ -> QuadTree a -- ^ current tree+ -> QuadTree a++treeInsert depthMax depth p0@(x0, y0) p1@(x1, y1) a tree+ = case tree of+ QNode p@(x, y) size tNW tNE tSW tSE+ -> let + + tNW' | y1 > y && x0 < x = treeInsert depthMax (depth + 1) p0 p1 a tNW+ | otherwise = tNW++ tNE' | y1 > y && x1 > x = treeInsert depthMax (depth + 1) p0 p1 a tNE+ | otherwise = tNE++ tSW' | y0 < y && x0 < x = treeInsert depthMax (depth + 1) p0 p1 a tSW+ | otherwise = tSW++ tSE' | y0 < y && x1 > x = treeInsert depthMax (depth + 1) p0 p1 a tSE+ | otherwise = tSE+ + in QNode p size tNW' tNE' tSW' tSE'+ + QLeaf p@(x, y) size elems+ | depth >= depthMax+ -> QLeaf p size (a : elems)+ + QNil p@(x, y) size+ | depth >= depthMax+ -> QLeaf p size [a]+ + | otherwise+ -> treeInsert depthMax depth p0 p1 a+ (let s2 = size / 2+ in QNode p size + (QNil (x - s2, y + s2) s2) (QNil (x + s2, y + s2) s2)+ (QNil (x - s2, y - s2) s2) (QNil (x + s2, y - s2) s2))+++-- flatten a quadtree into a list of its elements.+treeElems :: QuadTree a -> [[a]]+treeElems tree + = case tree of+ QNode _ _ tNW tNE tSW tSE+ -> treeElems tNW ++ treeElems tNE ++ treeElems tSW ++ treeElems tSE+ + QLeaf _ _ elems -> [elems]+ QNil{} -> []
+ Styrene/World.hs view
@@ -0,0 +1,90 @@+{-# LANGUAGE PatternGuards #-}++-- The world contains a map of all the actors, along with the current+-- quadtree so we can also draw it on the screen.+module World where++import QuadTree+import Actor+import Config++import qualified Data.Map as Map+import Data.Map (Map)++-- The world ---------------------------------------------------------------------------------------+data World + = World (Map Index Actor) -- actors+ (QuadTree Actor) -- tree++-- | The initial world+worldInit :: World+worldInit + = World actorMapInit treeInit++actorMapInit + = Map.fromList + $ map (\a -> (actorIx a, a))+ $ (walls ++ beads)++treeInit = treeZero treeSize+++-- Walls ------------------+walls :: [Actor]+walls = zipWith actorSetIndex (box ++ splitter) [10000 ..]++box :: [Actor]+box+ = let bs = boxSize+ in [ Wall 0 (- bs, -bs) (bs, -bs) -- bot+ , Wall 0 (- bs, bs) (bs, bs) -- top++ , Wall 0 (- bs, -bs) (-bs, bs) -- left+ , Wall 0 ( bs, -bs) ( bs, bs)] -- right++splitter :: [Actor]+splitter+ = [ Wall 0 (-15, -100) (-200, 0) + , Wall 0 ( 15, -100) ( 200, 0) ]+++-- Beads ------------------+beads :: [Actor]+beads + = let -- beads start off with their index just set to 0+ beads_raw+ = [Bead 0 0 beadRadius (beadPos ix iy) (0, 0)+ | ix <- [0 .. beadCountX - 1]+ , iy <- [0 .. beadCountY - 1 ] ]+ + -- set the unique index on the beads before returning them+ in zipWith actorSetIndex beads_raw [0..]+ +beadPos ix iy + = ( (ix * beadBoxSize) - (beadBoxSize * beadCountX / 2)+ , (iy * beadBoxSize) )+++-- QuadTree ----------------------------------------------------------------------------------------++-- | insert an actor into the tree+insertActor :: Actor -> QuadTree Actor -> QuadTree Actor++insertActor actor tree+ -- insert a bead into the tree+ | bead@(Bead ix _ radius pos@(x, y) vel) <- actor+ = let+ -- the bottom left and top right of the bead's bounding box.+ p0 = (x - radius, y - radius)+ p1 = (x + radius, y + radius)++ in treeInsert treeMaxDepth 0 p0 p1 bead tree++ | wall@(Wall ix (x0, y0) (x1, y1)) <- actor+ = let+ -- the bottom left and top right of the wall's bounding box.+ p0 = (min x0 x1, min y0 y1)+ p1 = (max x0 x1, max y0 y1)+ + in treeInsert treeMaxDepth 0 p0 p1 wall tree+
+ Tree/Main.hs view
@@ -0,0 +1,58 @@++-- | Tree Fractal.+-- Based on ANUPlot code by Clem Baker-Finch.+-- +import Graphics.Gloss++main = animateInWindow+ "Tree"+ (500, 650) + (20, 20)+ black+ (picture 4)+++-- The picture is a tree fractal, graded from brown to green+picture :: Int -> Float -> Picture +picture degree time+ = Translate 0 (-300)+ $ tree degree time (dim $ dim brown)+++-- Basic stump shape+stump :: Color -> Picture+stump color + = Color color+ $ Polygon [(30,0), (15,300), (-15,300), (-30,0)]+++-- Make a tree fractal.+tree :: Int -- Fractal degree+ -> Float -- time+ -> Color -- Color for the stump+ -> Picture++tree 0 time color = stump color+tree n time color + = let smallTree + = Rotate (sin time)+ $ Scale 0.5 0.5 + $ tree (n-1) (- time) (greener color)+ in Pictures+ [ stump color+ , Translate 0 300 $ smallTree+ , Translate 0 240 $ Rotate 20 smallTree+ , Translate 0 180 $ Rotate (-20) smallTree+ , Translate 0 120 $ Rotate 40 smallTree+ , Translate 0 60 $ Rotate (-40) smallTree ]+ ++-- A starting colour for the stump+brown :: Color+brown = makeColor8 139 100 35 255+++-- Make this color a little greener+greener :: Color -> Color+greener c = mixColors 1 10 green c+
+ Zen/Main.hs view
@@ -0,0 +1,68 @@++-- A nifty animated fractal of a tree, superimposed on a background +-- of three red rectangles.+import Graphics.Gloss+import Graphics.Gloss.Shapes++main :: IO ()+main + = animateInWindow + "Zen" + (800, 600) + (5, 5)+ (greyN 0.2)+ frame +++-- Produce one frame of the animation.+frame :: Float -> Picture+frame timeS+ = Pictures + -- the red rectangles+ [ Translate 0 150 backRec+ , Translate 0 0 backRec+ , Translate 0 (-150) backRec++ -- the tree+ , Translate 0 (-150) $ treeFrac 7 timeS+ ]+++-- One of the red backing rectangles, with a white outline.+backRec :: Picture+backRec + = Pictures+ [ Color red (rectangleSolid 400 100)+ , Color white (rectangleWire 400 100) ]+++-- The color for the outline of the tree's branches.+treeOutline :: Color+treeOutline = makeColor 0.3 0.3 1.0 1.0+++-- The color for the shading of the tree's branches.+-- The Alpha here is set to 0.5 so the branches are partly transparent.+treeColor :: Color+treeColor = makeColor 0.0 1.0 0.0 0.5+++-- The tree fractal.+-- The position of the branches changes depending on the animation time+-- as well as the iteration number of the fractal.+treeFrac :: Int -> Float -> Picture+treeFrac 0 timeS = Blank+treeFrac n timeS+ = Pictures+ [ Color treeColor $ rectangleUpperSolid 20 300+ , Color treeOutline $ rectangleUpperWire 20 300+ , Translate 0 30+ $ Rotate (200 * sin timeS / (fromIntegral n) )+ $ Scale 0.9 0.9 + $ treeFrac (n-1) timeS++ , Translate 0 70+ $ Rotate (-200 * sin timeS / (fromIntegral n))+ $ Scale 0.8 0.8 + $ treeFrac (n-1) timeS+ ]
+ gloss-examples.cabal view
@@ -0,0 +1,70 @@+Name: gloss-examples+Version: 1.0.0.0+License: MIT+License-file: LICENSE+Author: Ben Lippmeier+Maintainer: gloss@ouroborus.net+Build-Type: Simple+Cabal-Version: >=1.8+Stability: experimental+Category: Graphics+Description: + Examples using the gloss graphics library.+ A mixed bag of fractals, particle simulations and cellular automata.++Synopsis: Examples using the gloss library++Executable gloss-easy+ Build-depends: base >= 3 && < 5, gloss >= 1.0 && < 1.1+ Main-is: Easy/Main.hs+ ghc-options: -O2++Executable gloss-clock+ Build-depends: base >= 3 && < 5, gloss >= 1.0 && < 1.1+ Main-is: Clock/Main.hs+ ghc-options: -O2++Executable gloss-eden+ Build-depends: base >= 3 && < 5, gloss >= 1.0 && < 1.1, random > 1.0 && < 2.0+ Main-is: Main.hs+ other-modules: Cell Community World+ hs-source-dirs: Eden+ ghc-options: -O2++Executable gloss-flake+ Build-depends: base >= 3 && < 5, gloss >= 1.0 && < 1.1+ Main-is: Flake/Main.hs+ ghc-options: -O2++Executable gloss-hello+ Build-depends: base >= 3 && < 5, gloss >= 1.0 && < 1.1+ Main-is: Hello/Main.hs+ ghc-options: -O2++Executable gloss-lifespan+ Build-depends: base >= 3 && < 5, gloss >= 1.0 && < 1.1, random > 1.0 && < 2.0+ Main-is: Main.hs+ other-modules: Cell Community World+ hs-source-dirs: Lifespan+ ghc-options: -O2++Executable gloss-styrene+ Build-depends: + base >= 3 && < 5,+ gloss >= 1.0 && < 1.1,+ containers >= 0.0 && < 1.0,+ ghc-prim >= 0.2 && < 1.0+ Main-is: Main.hs+ other-modules: Actor Advance Collide Config Contact QuadTree World+ hs-source-dirs: Styrene+ ghc-options: -O2++Executable gloss-tree+ Build-depends: base >= 3 && < 5, gloss >= 1.0 && < 1.1+ Main-is: Tree/Main.hs+ ghc-options: -O2++Executable gloss-zen+ Build-depends: base >= 3 && < 5, gloss >= 1.0 && < 1.1+ Main-is: Zen/Main.hs+ ghc-options: -O2