Steven H. Cullinane’s Diamond Theory
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.
= sqrt 2
side triangleRect :: Diagram B
= polygon ( with
triangleRect & polyType .~ PolySides
135 @@ deg, 90 @@ deg]
[ 1 , side ]
[ )
When defining each triangle, the envelope do not take the linewidth (lw) into account so we set it to none. This will cause issues later on.
= triangleRect # rotateBy (1/2) # fc white # lc white # lw none
triangleLeft
= triangleRect # fc black #lc black # lw none triangleRight
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”.
= beside (r2 (1,-1)) (triangleLeft # align (r2 (1, -1)))
smallTile 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 # rotate x'
smallTile' 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
= matrix # alignX 0 # alignY 0
createMatrix x where matrix = (x !! 0 ||| x !! 1 )
===
!! 2 ||| x !! 3)
(x
= createMatrix (map smallTile' angles) mediumTile 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
= createMatrix [a, b, c, d]
largeTile angles xSymmetry ySymmetry where
= mediumTile $ chunks !! 0
a = if ySymmetry then a # reflectX else mediumTile $ chunks !! 1
b = if xSymmetry then a # reflectY else mediumTile $ chunks !! 2
c
d| ySymmetry && xSymmetry = a # rotateBy (-1/2)
| ySymmetry = c # reflectX
| xSymmetry = b # reflectY
| otherwise = mediumTile $ chunks !! 3
= chunksOf 4 angles
chunks
-- Needs a list of 16 angles and the number of axes
largeTile' :: ([Int], Int) -> Diagram B
= largeTile n xSymmetry ySymmetry
largeTile' x where
= fst x
n = snd x
nbAxes = nbAxes == 1 || nbAxes == 3
xSymmetry = nbAxes == 2 || nbAxes == 3 ySymmetry
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.
= (x-0.5)*4 + (x-1)*d
centerPos x where d = 1.5
randInts :: Int -> [Int]
= randomRs (0, 3) (mkStdGen seed)
randInts seed
example :: Diagram B
= position (zip (map p2 pos) (zipWith (curry largeTile') angles nbAxes))
example where
= 10
nb = [(centerPos x, centerPos y) | x <- [1..nb], y <- [1..nb]]
pos = take (nb*nb) $ chunksOf 16 $ randInts 15
angles = take (nb*nb) $ randInts 12 nbAxes
= mainWith (example :: Diagram B) main