A three mirror kaleidoscope with random confetti.
import Diagrams.Backend.SVG.CmdLine
{-# LANGUAGE NoMonomorphismRestriction #-}
import Control.Monad (replicateM)
import Control.Monad.Random
import Data.Colour.Palette.ColorSet
import Data.List (zipWith, zipWith3)
import Diagrams.Prelude
import System.Random
A helper function like iterate
but also takes the list index as a parameter.
iterateIdx :: (Int -> a -> a) -> a -> [a]
= go f t 0
iterateIdx f t where
= let t' = f i t
go f t i in t' : go f t' (i + 1)
Take any Diagram and cut out an equilateral triangle of side 1 from the center. This is the triangle inside of the three mirrors that make up a kaleidoscope. The image is created by first repeatedly reflecting this triangle and assembling the resulting triagles into a hexagon. Then the image plane is tiled with this hexagon.
kaleidoscope :: Diagram B -> Diagram B
= appends hex hexs
kaleidoscope d where
= zip dirs (replicate 6 hex)
hexs = iterate (rotateBy (1/6)) (rotateBy (1/12) unitX)
dirs = mconcat . take 6 $ iterateIdx next tri
hex = alignBR $ cutTriangle d
tri = reflectAbout (0 ^& 0) (rotateBy (- fromIntegral i / 6) xDir) next i
cutTriangle :: Diagram B -> Diagram B
= clipped (triangle 1) # lw none cutTriangle
We pass as arguments the number of pieces of confetti n
and a random seed r
.
Between 10 and 100 pieces seem to work nicely.
confettiScope :: Int -> Int -> Diagram B
confettiScope n r= kaleidoscope (mkConfetti n (mkStdGen r))
# centerXY <> (circle 2.75 # fc black)
# pad 1.1
To create an image for use in the kadeidescope we generate a bunch of disks with random location, size, color, and opacity. This is the confetti used as the image. Of course using circles is arbitrary, any shapes and sizes will do.
sizeValue :: (RandomGen g) => Rand g Double
= getRandomR (0.05, 0.25) sizeValue
coordValue :: (RandomGen g) => Rand g Double
= getRandomR (-0.5, 0.5) coordValue
We use monadRandom to hide the plumbing of the many random numbers we need.
The colors are chosen from the 330+ webColors
defined in the package
Data.Colour.Palette.ColorSet
.
confetti :: Int -> Rand StdGen (Diagram B)
= do
confetti n <- replicateM n sizeValue -- radius
ss <- replicateM n getRandom -- color index
cs <- replicateM n getRandom -- opacity
as <- replicateM n coordValue -- x coordinate
xs <- replicateM n coordValue -- y coordinate
ys let mkCirc :: Double -> Int -> Double -> Diagram B
= circle s # fc (webColors c) # lw none # opacity a
mkCirc s c a = zipWith mkP2 xs ys
pos = zipWith3 mkCirc ss cs as
conf return $ position (zip pos conf)
Make the confetti diagram and extract it from the monad.
mkConfetti :: Int -> (StdGen -> Diagram B)
= evalRand $ confetti n mkConfetti n
= confettiScope 39 0 example
= mainWith (example :: Diagram B) main