> import Diagrams.Backend.SVG.CmdLine
We recreate the "diamond theory" by Steven H. Cullinane (see the Recode project
http://recodeproject.com/artwork/v2n1diamond-theory).
> {-# LANGUAGE NoMonomorphismRestriction #-}
>
> import System.Random
> import Data.List.Split
The idea is to generate a matrix of tiles, where each tile is a square with
random black and white right triangles in it triangle. In practice, each
tile is composed of 4 smaller (square) tiles and each of them has 4 very small
tiles. We note the tiles "large", "medium" and "small" respectively in the code.
We first define the small tile. It is composed of a square split into two right
triangles. The upper left triangle is always white, the lower right triangle is
always black.
> side = sqrt 2
> triangleRect :: Diagram B
> triangleRect = polygon ( with
> & polyType .~ PolySides
> [ 135 @@ deg, 90 @@ deg]
> [ 1 , side ]
> )
>
When defining each triangle, the enveloppe do not take the linewidth (lw) into
account so we set it to none. This will cause issues later on.
> triangleLeft = triangleRect # rotateBy (1/2) # fc white # lc white # lw none
>
> triangleRight = triangleRect # fc black #lc black # lw none
For the small tile, we enforce the old behaviour for the origin of the tile
as we want the triangles to be composed at the point of tangency, enforced by
"align".
> smallTile = beside (r2 (1,-1)) (triangleLeft # align (r2 (1, -1)))
> triangleRight
For interesting results, the small tiles are rotated randomly
with angles in `\{0, \frac{\pi}{2}, \pi, \frac{3 \pi}{2} \}`.
> smallTile' :: Int -> Diagram B
> smallTile' x = smallTile # rotate x'
> where x' = fromIntegral x *pi/2 @@ rad
Now we can create the medium tile, where 4 small tiles are placed in a
matrix-like fashion. The origin must be placed at the center with align
> createMatrix x = matrix # alignX 0 # alignY 0
> where matrix = (x !! 0 ||| x !! 1 )
> ===
> (x !! 2 ||| x !! 3)
>
> mediumTile angles = createMatrix (map smallTile' angles)
We then create the large tiles as a composition of 4 medium tiles.
For even more interesting results, we use a random number of axis of symmetry
(between 0 and 2). Here, we take list of 16 angles as input, where each angle
corresponds to a random rotation for the small tiles.
Beware reflectX is actually a reflection in respect to the Y axis, so the
naming convention is inverted.
> largeTile :: [Int] -> Bool -> Bool -> Diagram B
> largeTile angles xSymmetry ySymmetry = createMatrix [a, b, c, d]
> where
> a = mediumTile $ chunks !! 0
> b = if ySymmetry then a # reflectX else mediumTile $ chunks !! 1
> c = if xSymmetry then a # reflectY else mediumTile $ chunks !! 2
> d
> | ySymmetry && xSymmetry = a # rotateBy (-1/2)
> | ySymmetry = c # reflectX
> | xSymmetry = b # reflectY
> | otherwise = mediumTile $ chunks !! 3
> chunks = chunksOf 4 angles
>
> -- Needs a list of 16 angles and the number of axes
> largeTile' :: ([Int], Int) -> Diagram B
> largeTile' x = largeTile n xSymmetry ySymmetry
> where
> n = fst x
> nbAxes = snd x
> xSymmetry = nbAxes == 1 || nbAxes == 3
> ySymmetry = nbAxes == 2 || nbAxes == 3
Finally, we create an array of large tiles by using `position`. The random
angles and number of axis of symmetries are generated here, at the higher level.
This allows us to only generate two random list. However, they must be split into
chunks accordingly.
As a final note, the bug with the linewidth will most likely appear in the final
results as very fine gapes between the small tiles.
> centerPos x = (x-0.5)*4 + (x-1)*d
> where d = 1.5
>
> randInts :: Int -> [Int]
> randInts seed = randomRs (0, 3) (mkStdGen seed)
>
> example :: Diagram B
> example = position (zip (map p2 pos) (zipWith (curry largeTile') angles nbAxes))
> where
> nb = 10
> pos = [(centerPos x, centerPos y) | x <- [1..nb], y <- [1..nb]]
> angles = take (nb*nb) $ chunksOf 16 $ randInts 15
> nbAxes = take (nb*nb) $ randInts 12
> main = mainWith (example :: Diagram B)