2012-06-01 13:24:58 +00:00
|
|
|
## Only the edges
|
|
|
|
|
|
|
|
<div style="display:none">
|
|
|
|
|
|
|
|
> import Graphics.Rendering.OpenGL
|
|
|
|
> import Graphics.UI.GLUT
|
|
|
|
> import Data.IORef
|
2012-06-25 08:59:41 +00:00
|
|
|
> -- Use UNPACK data because it is faster
|
|
|
|
> -- The ! is for strict instead of lazy
|
|
|
|
> data Complex = C {-# UNPACK #-} !Float
|
|
|
|
> {-# UNPACK #-} !Float
|
|
|
|
> deriving (Show,Eq)
|
2012-06-01 13:24:58 +00:00
|
|
|
> instance Num Complex where
|
2012-06-25 08:59:41 +00:00
|
|
|
> fromInteger n = C (fromIntegral n) 0.0
|
|
|
|
> (C x y) * (C z t) = C (z*x - y*t) (y*z + x*t)
|
|
|
|
> (C x y) + (C z t) = C (x+z) (y+t)
|
|
|
|
> abs (C x y) = C (sqrt (x*x + y*y)) 0.0
|
|
|
|
> signum (C x y) = C (signum x) 0.0
|
2012-06-01 13:24:58 +00:00
|
|
|
> complex :: Float -> Float -> Complex
|
2012-06-25 08:59:41 +00:00
|
|
|
> complex x y = C x y
|
2012-06-01 13:24:58 +00:00
|
|
|
>
|
|
|
|
> real :: Complex -> Float
|
2012-06-25 08:59:41 +00:00
|
|
|
> real (C x y) = x
|
2012-06-01 13:24:58 +00:00
|
|
|
>
|
|
|
|
> im :: Complex -> Float
|
2012-06-25 08:59:41 +00:00
|
|
|
> im (C x y) = y
|
2012-06-01 13:24:58 +00:00
|
|
|
>
|
|
|
|
> magnitude :: Complex -> Float
|
|
|
|
> magnitude = real.abs
|
|
|
|
> main :: IO ()
|
|
|
|
> main = do
|
|
|
|
> -- GLUT need to be initialized
|
|
|
|
> (progname,_) <- getArgsAndInitialize
|
|
|
|
> -- We will use the double buffered mode (GL constraint)
|
|
|
|
> initialDisplayMode $= [DoubleBuffered]
|
|
|
|
> -- We create a window with some title
|
|
|
|
> createWindow "Mandelbrot Set with Haskell and OpenGL"
|
|
|
|
> -- Each time we will need to update the display
|
|
|
|
> -- we will call the function 'display'
|
|
|
|
> displayCallback $= display
|
|
|
|
> -- We enter the main loop
|
|
|
|
> mainLoop
|
|
|
|
> display = do
|
|
|
|
> -- set the background color (dark solarized theme)
|
|
|
|
> clearColor $= Color4 0 0.1686 0.2117 1
|
|
|
|
> clear [ColorBuffer] -- make the window black
|
|
|
|
> loadIdentity -- reset any transformation
|
|
|
|
> preservingMatrix drawMandelbrot
|
|
|
|
> swapBuffers -- refresh screen
|
|
|
|
>
|
|
|
|
> width = 320 :: GLfloat
|
|
|
|
> height = 320 :: GLfloat
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
2012-06-15 13:25:51 +00:00
|
|
|
This time, instead of drawing all points,
|
|
|
|
we will simply draw the edges of the Mandelbrot set.
|
2012-06-12 14:17:25 +00:00
|
|
|
The method I use is a rough approximation.
|
|
|
|
I consider the Mandelbrot set to be almost convex.
|
2012-06-15 13:25:51 +00:00
|
|
|
The result will be good enough for the purpose of this tutorial.
|
2012-06-12 14:17:25 +00:00
|
|
|
|
2012-06-14 16:04:16 +00:00
|
|
|
We change slightly the `drawMandelbrot` function.
|
2012-06-01 13:24:58 +00:00
|
|
|
We replace the `Points` by `LineLoop`
|
|
|
|
|
|
|
|
> drawMandelbrot =
|
|
|
|
> -- We will print Points (not triangles for example)
|
|
|
|
> renderPrimitive LineLoop $ do
|
|
|
|
> mapM_ drawColoredPoint allPoints
|
|
|
|
> where
|
|
|
|
> drawColoredPoint (x,y,c) = do
|
|
|
|
> color c -- set the current color to c
|
|
|
|
> -- then draw the point at position (x,y,0)
|
|
|
|
> -- remember we're in 3D
|
|
|
|
> vertex $ Vertex3 x y 0
|
|
|
|
|
|
|
|
And now, we should change our list of points.
|
|
|
|
Instead of drawing every point of the visible surface,
|
|
|
|
we will choose only point on the surface.
|
|
|
|
|
|
|
|
> allPoints = positivePoints ++
|
|
|
|
> map (\(x,y,c) -> (x,-y,c)) (reverse positivePoints)
|
|
|
|
|
|
|
|
We only need to compute the positive point.
|
2012-06-14 16:04:16 +00:00
|
|
|
The Mandelbrot set is symmetric relatively to the abscisse axis.
|
2012-06-01 13:24:58 +00:00
|
|
|
|
|
|
|
> positivePoints :: [(GLfloat,GLfloat,Color3 GLfloat)]
|
|
|
|
> positivePoints = do
|
2012-06-12 14:17:25 +00:00
|
|
|
> x <- [-width..width]
|
2012-06-14 16:04:16 +00:00
|
|
|
> let y = maxZeroIndex (mandel x) 0 height (log2 height)
|
2012-06-12 14:17:25 +00:00
|
|
|
> if y < 1 -- We don't draw point in the absciss
|
|
|
|
> then []
|
|
|
|
> else return (x/width,y/height,colorFromValue $ mandel x y)
|
|
|
|
> where
|
|
|
|
> log2 n = floor ((log n) / log 2)
|
|
|
|
|
|
|
|
This function is interesting.
|
2012-06-01 13:24:58 +00:00
|
|
|
For those not used to the list monad here is a natural language version of this function:
|
|
|
|
|
2012-06-14 16:04:16 +00:00
|
|
|
<code class="no-highlight">
|
2012-06-01 13:24:58 +00:00
|
|
|
positivePoints =
|
|
|
|
for all x in the range [-width..width]
|
|
|
|
let y be smallest number s.t. mandel x y > 0
|
|
|
|
if y is on 0 then don't return a point
|
|
|
|
else return the value corresonding to (x,y,color for (x+iy))
|
2012-06-14 16:04:16 +00:00
|
|
|
</code>
|
2012-06-01 13:24:58 +00:00
|
|
|
|
|
|
|
In fact using the list monad you write like if you consider only one element at a time and the computation is done non deterministically.
|
2012-06-12 14:17:25 +00:00
|
|
|
To find the smallest number such that `mandel x y > 0` we use a simple dichotomy:
|
2012-06-01 13:24:58 +00:00
|
|
|
|
2012-06-14 16:04:16 +00:00
|
|
|
> -- given f min max nbtest,
|
|
|
|
> -- considering
|
|
|
|
> -- - f is an increasing function
|
|
|
|
> -- - f(min)=0
|
|
|
|
> -- - f(max)≠0
|
|
|
|
> -- then maxZeroIndex f min max nbtest returns x such that
|
|
|
|
> -- f(x - ε)=0 and f(x + ε)≠0
|
|
|
|
> -- where ε=(max-min)/2^(nbtest+1)
|
|
|
|
> maxZeroIndex func minval maxval 0 = (minval+maxval)/2
|
|
|
|
> maxZeroIndex func minval maxval n =
|
2012-06-01 13:24:58 +00:00
|
|
|
> if (func medpoint) /= 0
|
2012-06-14 16:04:16 +00:00
|
|
|
> then maxZeroIndex func minval medpoint (n-1)
|
|
|
|
> else maxZeroIndex func medpoint maxval (n-1)
|
2012-06-01 13:24:58 +00:00
|
|
|
> where medpoint = (minval+maxval)/2
|
|
|
|
|
2012-06-12 14:17:25 +00:00
|
|
|
No rocket science here. See the result now:
|
2012-06-01 13:24:58 +00:00
|
|
|
|
|
|
|
blogimage("HGLMandelEdges.png","The edges of the mandelbrot set")
|
|
|
|
|
|
|
|
<div style="display:none">
|
|
|
|
|
|
|
|
> colorFromValue n =
|
|
|
|
> let
|
|
|
|
> t :: Int -> GLfloat
|
|
|
|
> t i = 0.5 + 0.5*cos( fromIntegral i / 10 )
|
|
|
|
> in
|
|
|
|
> Color3 (t n) (t (n+5)) (t (n+10))
|
|
|
|
|
|
|
|
> mandel x y =
|
|
|
|
> let r = 2.0 * x / width
|
|
|
|
> i = 2.0 * y / height
|
|
|
|
> in
|
|
|
|
> f (complex r i) 0 64
|
|
|
|
|
|
|
|
> f :: Complex -> Complex -> Int -> Int
|
|
|
|
> f c z 0 = 0
|
|
|
|
> f c z n = if (magnitude z > 2 )
|
|
|
|
> then n
|
|
|
|
> else f c ((z*z)+c) (n-1)
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|