diff --git a/Attrac.cabal b/Attrac.cabal
new file mode 100644
--- /dev/null
+++ b/Attrac.cabal
@@ -0,0 +1,18 @@
+name:                Attrac
+version:             0.1.0
+author:              Ruben Henner Zilibowitz <rzilibowitz at yahoo dot com dot au>
+maintainer:          Ruben Henner Zilibowitz <rzilibowitz at yahoo dot com dot au>
+homepage:            http://patch-tag.com/r/rhz/StrangeAttractors
+license:             BSD3
+license-file:        LICENSE
+category:            Graphics
+extra-source-files:  README Setup.hs Attrac.hs SprottCodes.hs
+build-depends:       base >= 2 && < 4, haskell98, OpenGL, GLUT, colour, random
+tested-with:         GHC ==6.10.4
+build-type:          Simple
+synopsis:            Visualisation of Strange Attractors in 3-Dimensions
+description:         This software uses innovative techniques to achieve 3-D visualisation
+		     of the point sets known as Strange Attractors resulting from iteration
+		     of non-linear maps on points in Euclidean Space.
+executable:          Attrac
+main-is:             AttracCloudView.hs
diff --git a/Attrac.hs b/Attrac.hs
new file mode 100644
--- /dev/null
+++ b/Attrac.hs
@@ -0,0 +1,150 @@
+module Attrac where
+
+import Array
+import System.Random
+
+type Value = Double
+data Pt = Pt Value Value Value   deriving Show
+data Mat = Mat Value Value Value Value Value Value Value Value Value   deriving Show
+type Coeffs = Array Int Value
+
+--cs = array (0,29) (zip [0..] (readCode whichCode))
+--   where whichCode = codes!!37
+-- 16 = tetrahedron
+-- 29 is happy face
+
+f cs (Pt x y z) = Pt
+   ((cs!0) + (cs!1)*x + (cs!2)*x*x + (cs!3)*x*y + (cs!4)*x*z + (cs!5)*y + (cs!6)*y*y + (cs!7)*y*z + (cs!8)*z + (cs!9)*z*z)
+   ((cs!10) + (cs!11)*x + (cs!12)*x*x + (cs!13)*x*y + (cs!14)*x*z + (cs!15)*y + (cs!16)*y*y + (cs!17)*y*z + (cs!18)*z + (cs!19)*z*z)
+   ((cs!20) + (cs!21)*x + (cs!22)*x*x + (cs!23)*x*y + (cs!24)*x*z + (cs!25)*y + (cs!26)*y*y + (cs!27)*y*z + (cs!28)*z + (cs!29)*z*z)
+
+fjac cs (Pt x y z) = Mat
+   ((cs!1)+2*(cs!2)*x+(cs!3)*y+(cs!4)*z)
+   ((cs!3)*x+(cs!5)+2*(cs!6)*y+(cs!7)*z)
+   ((cs!4)*x+(cs!7)*y+(cs!8)+2*(cs!9)*z)
+   ((cs!11)+2*(cs!12)*x+(cs!13)*y+(cs!14)*z)
+   ((cs!13)*x+(cs!15)+2*(cs!16)*y+(cs!17)*z)
+   ((cs!14)*x+(cs!17)*y+(cs!18)+2*(cs!19)*z)
+   ((cs!21)+2*(cs!22)*x+(cs!23)*y+(cs!24)*z)
+   ((cs!23)*x+(cs!25)+2*(cs!26)*y+(cs!27)*z)
+   ((cs!24)*x+(cs!27)*y+(cs!28)+2*(cs!29)*z)
+
+normCols (Mat a11 a12 a13 a21 a22 a23 a31 a32 a33) = Mat (a11*a) (a12*b) (a13*c) (a21*a) (a22*b) (a23*c) (a31*a) (a32*b) (a33*c)
+   where a = recip (sqrt (a11*a11 + a21*a21 + a31*a31))
+         b = recip (sqrt (a12*a12 + a22*a22 + a32*a32))
+         c = recip (sqrt (a13*a13 + a23*a23 + a33*a33))
+
+fm cs (p,m) = (f cs p,normCols ((fjac cs p) <*> m))
+
+(<*>) :: Mat -> Mat -> Mat
+(<*>) (Mat a11 a12 a13 a21 a22 a23 a31 a32 a33) (Mat b11 b12 b13 b21 b22 b23 b31 b32 b33)
+   = Mat (a11*b11 + a12*b21 + a13*b31) (a11*b12 + a12*b22 + a13*b32) (a11*b13 + a12*b23 + a13*b33)
+         (a21*b11 + a22*b21 + a23*b31) (a21*b12 + a22*b22 + a23*b32) (a21*b13 + a22*b23 + a23*b33)
+         (a31*b11 + a32*b21 + a33*b31) (a31*b12 + a32*b22 + a33*b32) (a31*b13 + a32*b23 + a33*b33)
+
+zeroPt = Pt 0 0 0
+idMat = Mat 1 0 0 0 1 0 0 0 1
+almostZeroPt = peturb peturbationAmount zeroPt
+
+(<.>) :: Pt -> Pt -> Value
+(<.>) (Pt ax ay az) (Pt bx by bz) = ax*bx + ay*by + az*bz
+
+(<+>) :: Pt -> Pt -> Pt
+(<+>) (Pt ax ay az) (Pt bx by bz) = Pt (ax+bx) (ay+by) (az+bz)
+
+(<->) :: Pt -> Pt -> Pt
+(<->) (Pt ax ay az) (Pt bx by bz) = Pt (ax-bx) (ay-by) (az-bz)
+
+(<#>) :: Value -> Pt -> Pt
+(<#>) a (Pt x y z) = Pt (a*x) (a*y) (a*z)
+
+normalise :: Pt -> Pt
+normalise p = (recip (sqrt (p <.> p))) <#> p
+
+norm a = a <.> a
+
+data PMat = PMat
+   Value Value Value Value
+   Value Value Value Value
+   Value Value Value Value
+   Value Value Value Value  deriving Show
+
+idPMat = PMat
+  1 0 0 0
+  0 1 0 0
+  0 0 1 0
+  0 0 0 1
+
+(<*%>) :: PMat -> PMat -> PMat
+(<*%>) (PMat a11 a12 a13 a14 a21 a22 a23 a24 a31 a32 a33 a34 a41 a42 a43 a44)
+  (PMat b11 b12 b13 b14 b21 b22 b23 b24 b31 b32 b33 b34 b41 b42 b43 b44)
+   = PMat (a11*b11 + a12*b21 + a13*b31 + a14*b41)
+     (a11*b12 + a12*b22 + a13*b32 + a14*b42)
+     (a11*b13 + a12*b23 + a13*b33 + a14*b43)
+     (a11*b14 + a12*b24 + a13*b34 + a14*b44)
+     (a21*b11 + a22*b21 + a23*b31 + a24*b41)
+     (a21*b12 + a22*b22 + a23*b32 + a24*b42)
+     (a21*b13 + a22*b23 + a23*b33 + a24*b43)
+     (a21*b14 + a22*b24 + a23*b34 + a24*b44)
+     (a31*b11 + a32*b21 + a33*b31 + a34*b41)
+     (a31*b12 + a32*b22 + a33*b32 + a34*b42)
+     (a31*b13 + a32*b23 + a33*b33 + a34*b43)
+     (a31*b14 + a32*b24 + a33*b34 + a34*b44)
+     (a41*b11 + a42*b21 + a43*b31 + a44*b41)
+     (a41*b12 + a42*b22 + a43*b32 + a44*b42)
+     (a41*b13 + a42*b23 + a43*b33 + a44*b43)
+     (a41*b14 + a42*b24 + a43*b34 + a44*b44)
+
+(<*#>) :: PMat -> Pt -> Pt
+(<*#>) (PMat a11 a12 a13 a14 a21 a22 a23 a24 a31 a32 a33 a34 a41 a42 a43 a44) (Pt x y z) =
+  Pt (((x*a11)+(y*a12)+(z*a13)+a14) / s) (((x*a21)+(y*a22)+(z*a23)+a24) / s) (((x*a31)+(y*a32)+(z*a33)+a34) / s)
+  where s = ((x*a41)+(y*a42)+(z*a43)+a44)
+
+-----
+
+-- the amount to peturb orbits in the Lyapunov Exponent estimation
+peturbationAmount = 1e-12
+
+-- any point outside this radius from the origin is considered part
+-- of an unbounded orbit
+unboundedRadius = 4.5
+
+lyap :: Coeffs -> Int -> Pt -> Pt -> Maybe Value
+lyap cs t a b = (lyapHelper cs t a b) >>= (return . (/ (fromIntegral t)))
+  where
+    lyapHelper :: Coeffs -> Int -> Pt -> Pt -> Maybe Value
+    lyapHelper _  0 _ _ = Just 0
+    lyapHelper cs t a b | isBadNum na || na > unboundedRadius^2 = Nothing
+                        | otherwise = (lyapHelper cs (t-1) fa fb2) >>= (return . (s +))
+      where
+        na = norm a
+        fa = f cs a
+        fb = f cs b
+        dx2 = norm (fa <-> fb)
+        n = peturbationAmount <#> (normalise (fb <-> fa))
+        fb2 = fa <+> n
+        s = 0.5 * (logBase 2 (dx2 / (peturbationAmount^2)))
+
+isBadNum x = isNaN x || isInfinite x
+
+lyapunovAccuracy = 2048
+
+maxLyapunovExponent :: Coeffs -> Maybe Value
+maxLyapunovExponent cs = lyap cs lyapunovAccuracy p q
+  where
+    p = (iterate (f cs) almostZeroPt) !! 16
+    q = peturb peturbationAmount p
+
+numCoeffs = 30
+
+grabRandoms :: Int -> (Int,Int) -> StdGen -> ([Int],StdGen)
+grabRandoms 0 _ g = ([],g)
+grabRandoms n (x,y) g = let (r,g') = randomR (x,y) g in let (rs,g'') = grabRandoms (n-1) (x,y) g' in (r:rs,g'')
+
+randomiseCoeffs :: StdGen -> (Coeffs,Value)
+randomiseCoeffs g = case maxLyapunovExponent cs of {Just x | x > 1e-6 -> (cs,x); _ -> randomiseCoeffs g'}
+  where (as,g') = grabRandoms numCoeffs (-13,13) g
+        cs = listArray (0,numCoeffs-1) (map (\a -> 0.1 * (fromIntegral a)) as)
+
+peturb :: Value -> Pt -> Pt
+peturb e (Pt x y z) = Pt (x+e) y z
diff --git a/AttracCloudView.hs b/AttracCloudView.hs
new file mode 100644
--- /dev/null
+++ b/AttracCloudView.hs
@@ -0,0 +1,294 @@
+{-
+Author: Ruben Henner Zilibowitz
+Date: 20/2/10
+Compiles under GHC 6.10.4 with:
+ghc --make -O2 AttracCloudView.hs
+-}
+
+module Main where
+
+import Data.IORef  ( IORef, newIORef, readIORef, modifyIORef, writeIORef )
+import System.Exit ( exitWith, ExitCode(ExitSuccess) )
+import Graphics.Rendering.OpenGL as OpenGL
+import Graphics.UI.GLUT as GLUT
+import IO(hFlush,stdout)
+import Array
+import Data.Colour.RGBSpace.HSL
+import Data.Colour.SRGB as SRGB
+import System.Random
+import Char
+
+import Attrac
+import SprottCodes
+
+ffm cs (p1,Pt x y z) = let (p2,Mat tx _ _ ty _ _ tz _ _) = fm cs (p1,Mat x 1 1 y 0 0 z 0 0) in (p2,Pt tx ty tz)
+
+-----
+
+type Pt2 = (Value,Value)
+data State = State { leftMouseButton, middleMouseButton, rightMouseButton :: IORef KeyState,
+                     mouseLoc, mouseDrag :: IORef Position,
+                     curP :: IORef (Pt,Pt),
+                     coeffs :: IORef Coeffs,
+                     screen :: IORef (Int,Int),
+                     sprottCode :: IORef Int,
+                     curTransformMat :: IORef PMat,
+                     curBuffer :: IORef (Array (Int,Int) (Value,Integer)),
+                     framesElapsed :: IORef Integer }
+
+lightPt = Pt 10 10 (-10)
+eyePt = Pt 0 0 (-10)
+
+findOrbit :: Coeffs -> (Pt,Pt)
+findOrbit cs = (iterate (ffm cs) (almostZeroPt,Pt 1 0 0)) !! 128
+
+---
+--- make initial state
+---
+makeState :: (Int,Int) -> Coeffs -> IO State
+makeState dims@(width,height) cs = do
+   leftMouseButton_ <- newIORef Up
+   middleMouseButton_ <- newIORef Up
+   rightMouseButton_ <- newIORef Up
+   mouseLoc_ <- newIORef (Position 0 0)
+   mouseDrag_ <- newIORef (Position 0 0)
+   curP_ <- newIORef (findOrbit cs)
+   coeffs_ <- newIORef cs
+   screen_ <- newIORef dims
+   sprottCode_ <- newIORef 0
+   curTransformMat_ <- newIORef idPMat
+   curBuffer_ <- newIORef (listArray ((0,0),(width-1,height-1)) (repeat (0,0)))
+   framesElapsed_ <- newIORef 0
+   return $ State { leftMouseButton = leftMouseButton_,
+                    middleMouseButton = middleMouseButton_,
+                    rightMouseButton = rightMouseButton_,
+                    mouseLoc = mouseLoc_,
+                    mouseDrag = mouseDrag_,
+                    curP = curP_,
+                    coeffs = coeffs_,
+                    screen = screen_,
+                    sprottCode = sprottCode_,
+                    curTransformMat = curTransformMat_,
+                    curBuffer = curBuffer_,
+                    framesElapsed = framesElapsed_ }
+
+---
+--- display callback
+---
+
+iterations = 1000
+
+iterateFuncFor 0 _ z = ([],[],z)
+iterateFuncFor n f z = (a:w,b:x,y)
+   where (a,b) = f z
+         (w,x,y) = iterateFuncFor (n-1) f (a,b)
+
+shade :: Pt -> Pt -> Value
+shade p@(Pt x y z) t@(Pt tx ty tz)
+  | ((lightPt <-> p) <.> (eyePt <-> p) < 0) = ka
+  | gamma < 0 = error "sqrt (-1)"
+  | diffuse < 0 = error "neg diffuse"
+  | specular < 0 = error "neg specular"
+  | otherwise = ka + kd*diffuse + ks*specular
+   where
+     l = normalise (lightPt <-> p)
+     tt = t <.> t
+     ll = l <.> l
+     lt = l <.> t
+     gamma = tt^2*ll - tt*lt^2
+     rootGamma = sqrt gamma
+     alpha = negate (tt / rootGamma)
+     beta = lt / rootGamma
+     n_ = (alpha <#> l) <+> (beta <#> t)
+     n = if (l <.> n_ < 0) then (-1) <#> n_ else n_
+     r = ((2 * (l <.> n)) <#> n) <-> l
+     v = normalise (eyePt <-> p)
+     diffuse = l <.> n
+     shininess = 8
+     specular = (r <.> v) ^ shininess
+     ka = 0.09
+     ks = 0.4
+     kd = 1 - ka - ks
+
+transform :: (Int,Int) -> PMat -> Pt -> (Int,Int)
+transform (width,height) m p = (round $ (fromIntegral width) * 0.5 + 100*x, round $ (fromIntegral height) * 0.5 + 100*y)
+   where (Pt x y _) = m <*#> p
+
+colorSpeed = 1e-2
+
+display :: State -> IO ()
+display state = do
+   p <- readIORef (curP state)
+   cs <- readIORef (coeffs state)
+   let (ps,ts,q) = iterateFuncFor iterations (ffm cs) p
+   m <- readIORef (curTransformMat state)
+   dims@(width,height) <- readIORef (screen state)
+   let xs = filter (\((px,py),_) -> 0<=px&&px<width&&0<=py&&py<height)
+            [(transform dims m p,shade p t) | (p,t) <- zip ps ts]
+   modifyIORef (curBuffer state) (flip (Array.accum (\(y,c) x -> (x+y,c+1))) xs)
+   buf <- readIORef (curBuffer state)
+   renderPrimitive Points$sequence_ [let (x,c) = buf!(px,py)
+                                     in let SRGB.RGB r g b = hsl (colorSpeed * fromIntegral c) 1 (x / (fromIntegral c))
+                                        in (color$Color3 r g b) >>
+                                           (vertex$Vertex2 (fromIntegral px :: GLint)
+                                            (fromIntegral py :: GLint)) | ((px,py),_) <- xs]
+   writeIORef (curP state) q
+   modifyIORef (framesElapsed state) (1 +)
+   readIORef (framesElapsed state) >>= \f -> if (mod f 10 == 0) then putChar '.' >> hFlush stdout else return ()
+   
+   swapBuffers
+   postRedisplay Nothing
+
+---
+--- reshape callback
+---
+reshape :: State -> Size -> IO ()
+reshape state size@(Size w h) = do
+   viewport $= (Position 0 0, size)
+   matrixMode $= Modelview 0
+   loadIdentity
+   matrixMode $= Projection
+   loadIdentity
+   ortho2D 0 (fromIntegral w) 0 (fromIntegral h)
+   writeIORef (screen state) (fromIntegral w,fromIntegral h)
+   clearAll state
+
+---
+--- keyboard and mouse callback
+---
+
+(./) a b = (fromIntegral a) / (fromIntegral b)
+
+coeffsToSprottCode :: [Value] -> [Char]
+coeffsToSprottCode xs = 'I' : [chr (round (x*10) + ord 'M') | x <- xs]
+
+keyboardMouse :: State -> KeyboardMouseCallback
+keyboardMouse state (Char c) Down _ _ = case c of
+   'm' -> print =<< (readIORef $ curTransformMat state)
+   'p' -> print =<< (readIORef $ curP state)
+   'f' -> print =<< (readIORef $ framesElapsed state)
+   'r' -> postRedisplay Nothing
+   ' ' -> writeIORef (curTransformMat state) idPMat >> clearAll state
+   'c' -> do
+     g <- newStdGen
+     let (cs,ly) = randomiseCoeffs g
+     putStrLn $ "\nSprott code: " ++ coeffsToSprottCode (elems cs) ++ "\nLyapunov Exponent: " ++ show ly
+     writeIORef (coeffs state) cs
+     writeIORef (curP state) (findOrbit cs)
+     clearAll state
+   's' -> do
+     n <- readIORef (sprottCode state)
+     let n' = (n + 1) `mod` (length codes)
+     let cs = listArray (0,numCoeffs-1) (readCode (codes !! n'))
+     putStrLn $ "\nSprott code: " ++ codes!!n' ++ "\nLyapunov Exponent: " ++ show (maxLyapunovExponent cs)
+     writeIORef (coeffs state) cs
+     writeIORef (curP state) (findOrbit cs)
+     writeIORef (sprottCode state) n'
+     clearAll state
+   '\27' -> putStrLn "" >> exitWith ExitSuccess
+   _     -> return ()
+keyboardMouse state (MouseButton LeftButton) buttonState _ pos =
+   do writeIORef (leftMouseButton state) buttonState
+      writeIORef (mouseLoc state) pos
+      writeIORef (mouseDrag state) pos
+keyboardMouse state (MouseButton MiddleButton) buttonState _ pos =
+   do writeIORef (middleMouseButton state) buttonState
+      writeIORef (mouseLoc state) pos
+      writeIORef (mouseDrag state) pos
+keyboardMouse state (MouseButton RightButton) buttonState _ pos =
+   do writeIORef (rightMouseButton state) buttonState
+      writeIORef (mouseLoc state) pos
+      writeIORef (mouseDrag state) pos
+keyboardMouse _ _ _ _ _ = return ()
+
+clearAll state = do
+  clear [ColorBuffer]
+  (width,height) <- readIORef (screen state)
+  writeIORef (curBuffer state) (listArray ((0,0),(width-1,height-1)) (repeat (0,0)))
+
+---
+--- motion callback
+---
+motion :: State -> MotionCallback
+motion state pos@(Position newX newY) = do
+   clearAll state
+   Position oldX oldY <- readIORef (mouseDrag state)
+   writeIORef (mouseDrag state) pos
+   let dp = Position (newX-oldX) (newY-oldY)
+   loc <- readIORef (mouseLoc state)
+   lmb <- readIORef (leftMouseButton state)
+   mmb <- readIORef (middleMouseButton state)
+   rmb <- readIORef (rightMouseButton state)
+   case lmb of
+     Down -> modifyIORef (curTransformMat state) ((translateXYMat dp) <*%>)
+     Up -> return ()
+   case mmb of
+     Down -> modifyIORef (curTransformMat state) ((scaleMat dp) <*%>)
+     Up -> return ()
+   case rmb of
+     Down -> modifyIORef (curTransformMat state) ((rotateXYMat dp) <*%>)
+     Up -> return ()
+
+translateXYMat :: Position -> PMat
+translateXYMat (Position x y) = PMat
+  1 0 0 (fromIntegral x * 0.01)
+  0 1 0 (fromIntegral y * (-0.01))
+  0 0 1 0
+  0 0 0 1
+
+scaleMat :: Position  -> PMat
+scaleMat (Position x y) = PMat
+  1 0 0 0
+  0 1 0 0
+  0 0 1 0
+  0 0 0 (exp (fromIntegral x * (-0.01)))
+
+rotateXYMat :: Position -> PMat
+rotateXYMat (Position x y) = (PMat
+  1    0   0   0
+  0    cx (-sx) 0
+  0    sx  cx   0
+  0    0   0    1) <*%> (PMat
+  cy   0   sy   0
+  0    1   0    0
+ (-sy) 0   cy   0
+  0    0   0    1)
+  where cx = cos $ 0.001 * fromIntegral (negate y)
+        sx = sin $ 0.001 * fromIntegral (negate y)
+        cy = cos $ 0.001 * fromIntegral (negate x)
+        sy = sin $ 0.001 * fromIntegral (negate x)
+
+---
+--- main
+---
+
+initialWidth = 800 :: Int
+initialHeight = 800 :: Int
+
+main :: IO ()
+main = do
+   (_progName, args) <- getArgsAndInitialize
+   initialDisplayMode $= [ RGBMode ]
+   
+   initialWindowPosition $= Position 0 0
+   initialWindowSize $= Size (fromIntegral initialWidth) (fromIntegral initialHeight)
+   createWindow _progName
+   
+   let cs = listArray (0,numCoeffs-1) $ readCode (codes !! 0)
+   state <- makeState (initialWidth,initialHeight) cs
+   displayCallback $= display state
+   reshapeCallback $= Just (reshape state)
+   keyboardMouseCallback $= Just (keyboardMouse state)
+   motionCallback $= Just (motion state)
+   
+   clearColor $= backgroundColor
+   clear [ColorBuffer]
+   
+   putStrLn "Welcome. Instructions:"
+   putStr instructions
+   mainLoop
+
+backgroundColor = Color4 0 0 0.12 1
+
+instructions :: String
+instructions = "'m' -> output transform matrix\n'p' -> output current point\n'f' -> output frames elapsed\n'r' -> post redisplay\n' ' -> reset transform matrix\n'c' -> randomise coefficients\n's' -> select next Sprott code from list\nESC -> exit\n\n'left mouse button' -> drag mouse to translate in XY plane\n'middle mouse button' -> drag mouse to scale\n'right mouse button' -> drag mouse to rotate around X and Y axes\n"
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,11 @@
+Copyright (c) 2010, Ruben Henner Zilibowitz
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+Neither the name of the University of New South Wales nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/README b/README
new file mode 100644
--- /dev/null
+++ b/README
@@ -0,0 +1,22 @@
+Attrac
+
+This software uses innovative techniques to achieve 3-D visualisation
+of the point sets known as Strange Attractors resulting from iteration
+of non-linear maps on points in Euclidean Space.
+
+Instructions
+'m' -> output transform matrix 
+'p' -> output current point    
+'f' -> output frames elapsed   
+'r' -> post redisplay          
+' ' -> reset transform matrix  
+'c' -> randomise coefficients  
+'s' -> select next Sprott code from list 
+ESC -> exit
+
+'left mouse button' -> drag mouse to translate in XY plane
+'middle mouse button' -> drag mouse to scale
+'right mouse button' -> drag mouse to rotate around X and Y axes
+
+25/02/10
+Ruben Henner Zilibowitz
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/SprottCodes.hs b/SprottCodes.hs
new file mode 100644
--- /dev/null
+++ b/SprottCodes.hs
@@ -0,0 +1,53 @@
+module SprottCodes where
+
+import Char
+
+codes =
+ [	"IQDFKLQTIPQINQINWSSPNJTROQIOVQF",
+	"IMIGPMGPLQRHNVKLETWGMFESSIHUORQ",
+	"IFHTXHLVDKYSIPTUFJCFCLJGVQVHLYF",
+	"IFJLRNTKMSNJOXRDYPVEOOVTPLMGEAC",
+	"IGMNIKFCCNCPQFCJRQFUALCCLJPYVYD",
+	"IGNXQDPRVJPMBASUKJCRDRWVTDRQTTD",
+	"IGVQYNBNOMJCSSJIBFIDXWGGCWUOACV",
+	"IHHFGLDKRNJIYWJPMWNUOKJMLAAHQQD",
+	"IHJJDTICQIJETXFYNUSJKJSXGADBDKR",
+	"IHJNHTBGIFNDQMDOYWRYYTFSSRXQBEK",
+	"IHORHNHPPRVIVNNJUYHHMMJEWMMSUEU",
+	"IIPPSGTMPCELDIPWPUPJCNQNFOBCYCK",
+	"IJEKESGYYFWLOGVKLMEWJMBKHSOIVTI",
+	"IJIFTNNCYIGTVRLCMNJLKUQDGJIQSJC",
+	"IJKRADSXGDBHIJTQJJDICEJKYSTXFNU",
+	"IJMUYUNOXHMLLOJREMMWFJRWTHSVXOL",
+	"IJMYRKHSAUVRKPFVIJDMANDCIGJTIOB",
+	"IJVSURQUIINRDBRJAWRAKMLAHHUAOON",
+	"IKNXVQUEXYETWOOSGNSBDMHTMCPFLNG",
+	"IKRTYCFPFLTLSMOKRPEKMGPQHMUQYGY",
+	"ILITWKWUUUQIVQTIUWOLCNGELBMKGXC",
+	"ILNQAAYRNMTGVRVNPUNNKQFRBBTTYEW",
+	"ILRRHAEYWNTPWFLHTCSLYLFAKQITQTW",
+	"ILSQGYJJPISRVKJNPEXGIOSFDRQYGLO",
+	"ILURCEGOHOIQFJKBSNYGSNRUKKIKIHW",
+	"ILXRNRBKDJISQKAHRGWUTJONSIKVUBC",
+	"IMDBUAMMCSMTVFMONEEUDNEISFXDLCJ",
+	"IMROJCCRWSIMRPTLLENELIVYDEFWQHR",
+	"IMTISVBKHOIJFWSYEKEGYLWJKEOGVLM",
+	"IMYTXUJFERAEQWGBOYONOKBMHUVVXSX",
+	"INBSGKIRPRTKOEHRHUUGUHUQJNUSMOF",
+	"INDIJZIDVPJTZFFKYFLLJRPWFSHRKKU",
+	"INDVLLVRFLHPLLFCREMPLGLWNAFRPST",
+	"INSIUROMOJQIEEHYJKLRKQWULFDDGVB",
+	"IOHGWFIHJPSGWTOJBXWJKPBLKFRUKKQ",
+	"IOLORGSFDQYLISYPSQGJJRGINXVKJPE",
+	"IOQRYLZUZAQKSKBLLHVHOLNWOMRLXMI",
+	"IPGPJYPMITFPTBEEDRWRTUGSXCJFTWE",
+	"IPWRRDUTWIKBCTXUMTHLELPHJPADMQC",
+	"IQCBIKKBNREKJEWFTMAJYFGIEBHLFEH",
+	"IRQEDYQNTRPNDHNWNOFJOOLNTEEBPSA",
+	"ISKKGLKSUNOQLCDKUOBPINHIDBKPOKR",
+	"ISNQBNOJFJWMPQDTTFNMNNOISRYCDUN",
+	"IUWECTGSXJFPTFYIGPJPMPRTEWTBEDR",
+	"IWDWOGDGWGORJOBTUHFQBPRNTCBYQHP",
+	"IWHVUQKFCDAJPIATNPSLSFPLLUUGMGM"]
+
+readCode s = map (\c -> (fromIntegral (ord c - ord 'M'))*0.1) (tail s)
