her.esy.fun/src/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/index.html

1241 lines
146 KiB
HTML
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>YBlog - Haskell Progressive Example</title>
<meta name="keywords" content="Haskell, programming, functional, tutorial, fractal" />
<link rel="shortcut icon" type="image/x-icon" href="../../../../Scratch/img/favicon.ico" />
<link rel="stylesheet" type="text/css" href="/css/y.css" />
<link rel="stylesheet" type="text/css" href="/css/legacy.css" />
<link rel="alternate" type="application/rss+xml" title="RSS" href="/rss.xml" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="apple-touch-icon" href="../../../../Scratch/img/about/FlatAvatar@2x.png" />
<!--[if lt IE 9]>
<script src="http://ie7-js.googlecode.com/svn/version/2.1(beta4)/IE9.js"></script>
<![endif]-->
<!-- IndieAuth -->
<link href="https://twitter.com/yogsototh" rel="me">
<link href="https://github.com/yogsototh" rel="me">
<link href="mailto:yann.esposito@gmail.com" rel="me">
<link rel="pgpkey" href="../../../../pubkey.txt">
</head>
<body lang="en" class="article">
<div id="content">
<div id="header">
<div id="choix">
<span id="choixlang">
<a href="../../../../Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/">French</a>
</span>
<span class="tomenu"><a href="#navigation">↓ Menu ↓</a></span>
<span class="flush"></span>
</div>
</div>
<div id="titre">
<h1>Haskell Progressive Example</h1>
<h2>An OpenGL 3D extension of the Mandelbrot set</h2>
</div>
<div class="flush"></div>
<div id="afterheader" class="article">
<div class="corps">
<div>
<img src="../../../../Scratch/img/blog/Haskell-OpenGL-Mandelbrot/BenoitBMandelbrot.jpg" alt="The B in Benoît B. Mandelbrot stand for Benoît B. Mandelbrot" />
</div>
<div class="intro">
<p><span class="sc"><abbr title="Too long; didn't read">tl;dr</abbr>: </span> A progressive Haskell example. A Mandelbrot set extended in 3D, rendered using OpenGL and coded with Haskell. In the end the code will be very clean. The significant stuff will be in a pure functional bubble. The display details will be put in an external module playing the role of a wrapper. Imperative language could also benefit from this functional organization.</p>
</div>
<h2 id="introduction">Introduction</h2>
<p>In my <a href="../../../../Scratch/en/blog/Haskell-the-Hard-Way/">preceding article</a> I introduced Haskell.</p>
<p>This article goes further. It will show how to use functional programming with interactive programs. But more than that, it will show how to organize your code in a functional way. This article is more about functional paradigm than functional language. The code organization can be used in most imperative language.</p>
<p>As Haskell is designed for functional paradigm, it is easier to use in this context. In reality, the firsts sections will use an imperative paradigm. As you can use functional paradigm in imperative language, you can also use imperative paradigm in functional languages.</p>
<p>This article is about creating an useful and clean program. It can interact with the user in real time. It uses OpenGL, a library with imperative programming foundations. Despite this fact, most of the final code will remain in the pure part (no <code>IO</code>).</p>
<p>I believe the main audience for this article are:</p>
<ul>
<li>Haskell programmer looking for an OpengGL tutorial.</li>
<li>People interested in program organization (programming language agnostic).</li>
<li>Fractal lovers and in particular 3D fractal.</li>
<li>People interested in user interaction in a functional paradigm.</li>
</ul>
<p>I had in mind for some time now to make a Mandelbrot set explorer. I had already written a <a href="http://github.com/yogsototh/mandelbrot.git">command line Mandelbrot set generator in Haskell</a>. This utility is highly parallel; it uses the <code>repa</code> package<a href="#fn1" class="footnote-ref" id="fnref1"><sup>1</sup></a>.</p>
<p>This time, we will not parallelize the computation. Instead, we will display the Mandelbrot set extended in 3D using OpenGL and Haskell. You will be able to move it using your keyboard. This object is a Mandelbrot set in the plan (z=0), and something nice to see in 3D.</p>
<p>Here are some screenshots of the result:</p>
<figure>
<img src="../../../../Scratch/img/blog/Haskell-OpenGL-Mandelbrot/GoldenMandelbulb.png" alt="The entire Mandelbulb" />
<figcaption>
The entire Mandelbulb
</figcaption>
</figure>
<figure>
<img src="../../../../Scratch/img/blog/Haskell-OpenGL-Mandelbrot/3DMandelbulbDetail.png" alt="A Mandelbulb detail" />
<figcaption>
A Mandelbulb detail
</figcaption>
</figure>
<figure>
<img src="../../../../Scratch/img/blog/Haskell-OpenGL-Mandelbrot/3DMandelbulbDetail2.png" alt="Another detail of the Mandelbulb" />
<figcaption>
Another detail of the Mandelbulb
</figcaption>
</figure>
<p>And you can see the intermediate steps to reach this goal:</p>
<div>
<img src="../../../../Scratch/img/blog/Haskell-OpenGL-Mandelbrot/HGL_Plan.png" alt="The parts of the article" />
</div>
<p>From the 2nd section to the 4th it will be <em>dirtier</em> and <em>dirtier</em>. We start cleaning the code at the 5th section.</p>
<hr />
<p><a href="code/01_Introduction/hglmandel.lhs" class="cut">Download the source code of this section → 01_Introduction/<strong>hglmandel.lhs</strong></a></p>
<h2 id="first-version">First version</h2>
<p>We can consider two parts. The first being mostly some boilerplate<a href="#fn2" class="footnote-ref" id="fnref2"><sup>2</sup></a>. And the second part more focused on OpenGL and content.</p>
<h3 id="lets-play-the-song-of-our-people">Lets play the song of our people</h3>
<div class="codehighlight">
<div class="sourceCode" id="cb1"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb1-1" title="1"><span class="kw">import</span> <span class="dt">Graphics.Rendering.OpenGL</span></a>
<a class="sourceLine" id="cb1-2" title="2"><span class="kw">import</span> <span class="dt">Graphics.UI.GLUT</span></a>
<a class="sourceLine" id="cb1-3" title="3"><span class="kw">import</span> <span class="dt">Data.IORef</span></a></code></pre></div>
</div>
<p>For efficiency reason<a href="#fn3" class="footnote-ref" id="fnref3"><sup>3</sup></a>, I will not use the default Haskell <code>Complex</code> data type.</p>
<div class="codehighlight">
<div class="sourceCode" id="cb2"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb2-1" title="1"><span class="kw">data</span> <span class="dt">Complex</span> <span class="ot">=</span> <span class="dt">C</span> <span class="dt">Float</span> <span class="dt">Float</span> <span class="kw">deriving</span> (<span class="dt">Show</span>,<span class="dt">Eq</span>)</a></code></pre></div>
</div>
<div class="codehighlight">
<div class="sourceCode" id="cb3"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb3-1" title="1"><span class="kw">instance</span> <span class="dt">Num</span> <span class="dt">Complex</span> <span class="kw">where</span></a>
<a class="sourceLine" id="cb3-2" title="2"> <span class="fu">fromInteger</span> n <span class="ot">=</span> <span class="dt">C</span> (<span class="fu">fromIntegral</span> n,<span class="fl">0.0</span>)</a>
<a class="sourceLine" id="cb3-3" title="3"> (<span class="dt">C</span> x y) <span class="op">*</span> (<span class="dt">C</span> z t) <span class="ot">=</span> <span class="dt">C</span> (z<span class="op">*</span>x <span class="op">-</span> y<span class="op">*</span>t) (y<span class="op">*</span>z <span class="op">+</span> x<span class="op">*</span>t)</a>
<a class="sourceLine" id="cb3-4" title="4"> (<span class="dt">C</span> x y) <span class="op">+</span> (<span class="dt">C</span> z t) <span class="ot">=</span> <span class="dt">C</span> (x<span class="op">+</span>z) (y<span class="op">+</span>t)</a>
<a class="sourceLine" id="cb3-5" title="5"> <span class="fu">abs</span> (<span class="dt">C</span> x y) <span class="ot">=</span> <span class="dt">C</span> (<span class="fu">sqrt</span> (x<span class="op">*</span>x <span class="op">+</span> y<span class="op">*</span>y)) <span class="fl">0.0</span></a>
<a class="sourceLine" id="cb3-6" title="6"> <span class="fu">signum</span> (<span class="dt">C</span> x y) <span class="ot">=</span> <span class="dt">C</span> (<span class="fu">signum</span> x) (<span class="fl">0.0</span>)</a></code></pre></div>
</div>
<p>We declare some useful functions for manipulating complex numbers:</p>
<div class="codehighlight">
<div class="sourceCode" id="cb4"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb4-1" title="1"><span class="ot">complex ::</span> <span class="dt">Float</span> <span class="ot">-&gt;</span> <span class="dt">Float</span> <span class="ot">-&gt;</span> <span class="dt">Complex</span></a>
<a class="sourceLine" id="cb4-2" title="2">complex x y <span class="ot">=</span> <span class="dt">C</span> x y</a>
<a class="sourceLine" id="cb4-3" title="3"></a>
<a class="sourceLine" id="cb4-4" title="4"><span class="ot">real ::</span> <span class="dt">Complex</span> <span class="ot">-&gt;</span> <span class="dt">Float</span></a>
<a class="sourceLine" id="cb4-5" title="5">real (<span class="dt">C</span> x y) <span class="ot">=</span> x</a>
<a class="sourceLine" id="cb4-6" title="6"></a>
<a class="sourceLine" id="cb4-7" title="7"><span class="ot">im ::</span> <span class="dt">Complex</span> <span class="ot">-&gt;</span> <span class="dt">Float</span></a>
<a class="sourceLine" id="cb4-8" title="8">im (<span class="dt">C</span> x y) <span class="ot">=</span> y</a>
<a class="sourceLine" id="cb4-9" title="9"></a>
<a class="sourceLine" id="cb4-10" title="10"><span class="ot">magnitude ::</span> <span class="dt">Complex</span> <span class="ot">-&gt;</span> <span class="dt">Float</span></a>
<a class="sourceLine" id="cb4-11" title="11">magnitude <span class="ot">=</span> real<span class="op">.</span><span class="fu">abs</span></a></code></pre></div>
</div>
<h3 id="let-us-start">Let us start</h3>
<p>We start by giving the main architecture of our program:</p>
<div class="codehighlight">
<div class="sourceCode" id="cb5"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb5-1" title="1"><span class="ot">main ::</span> <span class="dt">IO</span> ()</a>
<a class="sourceLine" id="cb5-2" title="2">main <span class="ot">=</span> <span class="kw">do</span></a>
<a class="sourceLine" id="cb5-3" title="3"> <span class="co">-- GLUT need to be initialized</span></a>
<a class="sourceLine" id="cb5-4" title="4"> (progname,_) <span class="ot">&lt;-</span> getArgsAndInitialize</a>
<a class="sourceLine" id="cb5-5" title="5"> <span class="co">-- We will use the double buffered mode (GL constraint)</span></a>
<a class="sourceLine" id="cb5-6" title="6"> initialDisplayMode <span class="op">$=</span> [<span class="dt">DoubleBuffered</span>]</a>
<a class="sourceLine" id="cb5-7" title="7"> <span class="co">-- We create a window with some title</span></a>
<a class="sourceLine" id="cb5-8" title="8"> createWindow <span class="st">&quot;Mandelbrot Set with Haskell and OpenGL&quot;</span></a>
<a class="sourceLine" id="cb5-9" title="9"> <span class="co">-- Each time we will need to update the display</span></a>
<a class="sourceLine" id="cb5-10" title="10"> <span class="co">-- we will call the function 'display'</span></a>
<a class="sourceLine" id="cb5-11" title="11"> displayCallback <span class="op">$=</span> display</a>
<a class="sourceLine" id="cb5-12" title="12"> <span class="co">-- We enter the main loop</span></a>
<a class="sourceLine" id="cb5-13" title="13"> mainLoop</a></code></pre></div>
</div>
<p>Mainly, we initialize our OpenGL application. We declared that the function <code>display</code> will be used to render the graphics:</p>
<div class="codehighlight">
<div class="sourceCode" id="cb6"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb6-1" title="1">display <span class="ot">=</span> <span class="kw">do</span></a>
<a class="sourceLine" id="cb6-2" title="2"> clear [<span class="dt">ColorBuffer</span>] <span class="co">-- make the window black</span></a>
<a class="sourceLine" id="cb6-3" title="3"> loadIdentity <span class="co">-- reset any transformation</span></a>
<a class="sourceLine" id="cb6-4" title="4"> preservingMatrix drawMandelbrot</a>
<a class="sourceLine" id="cb6-5" title="5"> swapBuffers <span class="co">-- refresh screen</span></a></code></pre></div>
</div>
<p>Also here, there is only one interesting line; the draw will occur in the function <code>drawMandelbrot</code>.</p>
<p>This function will provide a list of draw actions. Remember that OpenGL is imperative by design. Then, one of the consequence is you must write the actions in the right order. No easy parallel drawing here. Here is the function which will render something on the screen:</p>
<div class="codehighlight">
<div class="sourceCode" id="cb7"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb7-1" title="1">drawMandelbrot <span class="ot">=</span></a>
<a class="sourceLine" id="cb7-2" title="2"> <span class="co">-- We will print Points (not triangles for example)</span></a>
<a class="sourceLine" id="cb7-3" title="3"> renderPrimitive <span class="dt">Points</span> <span class="op">$</span> <span class="kw">do</span></a>
<a class="sourceLine" id="cb7-4" title="4"> <span class="fu">mapM_</span> drawColoredPoint allPoints</a>
<a class="sourceLine" id="cb7-5" title="5"> <span class="kw">where</span></a>
<a class="sourceLine" id="cb7-6" title="6"> drawColoredPoint (x,y,c) <span class="ot">=</span> <span class="kw">do</span></a>
<a class="sourceLine" id="cb7-7" title="7"> color c <span class="co">-- set the current color to c</span></a>
<a class="sourceLine" id="cb7-8" title="8"> <span class="co">-- then draw the point at position (x,y,0)</span></a>
<a class="sourceLine" id="cb7-9" title="9"> <span class="co">-- remember we're in 3D</span></a>
<a class="sourceLine" id="cb7-10" title="10"> vertex <span class="op">$</span> <span class="dt">Vertex3</span> x y <span class="dv">0</span></a></code></pre></div>
</div>
<p>The <code>mapM_</code> function is mainly the same as map but inside a monadic context. More precisely, this can be transformed as a list of actions where the order is important:</p>
<pre><code>drawMandelbrot =
renderPrimitive Points $ do
color color1
vertex $ Vertex3 x1 y1 0
...
color colorN
vertex $ Vertex3 xN yN 0</code></pre>
<p>We also need some kind of global variables. In fact, global variable are a proof of a design problem. We will get rid of them later.</p>
<div class="codehighlight">
<div class="sourceCode" id="cb9"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb9-1" title="1">width <span class="ot">=</span> <span class="dv">320</span><span class="ot"> ::</span> <span class="dt">GLfloat</span></a>
<a class="sourceLine" id="cb9-2" title="2">height <span class="ot">=</span> <span class="dv">320</span><span class="ot"> ::</span> <span class="dt">GLfloat</span></a></code></pre></div>
</div>
<p>And of course our list of colored points. In OpenGL the default coordinate are from -1 to 1.</p>
<div class="codehighlight">
<div class="sourceCode" id="cb10"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb10-1" title="1"><span class="ot">allPoints ::</span> [(<span class="dt">GLfloat</span>,<span class="dt">GLfloat</span>,<span class="dt">Color3</span> <span class="dt">GLfloat</span>)]</a>
<a class="sourceLine" id="cb10-2" title="2">allPoints <span class="ot">=</span> [ (x<span class="op">/</span>width,y<span class="op">/</span>height,colorFromValue <span class="op">$</span> mandel x y) <span class="op">|</span></a>
<a class="sourceLine" id="cb10-3" title="3"> x <span class="ot">&lt;-</span> [<span class="op">-</span>width<span class="op">..</span>width],</a>
<a class="sourceLine" id="cb10-4" title="4"> y <span class="ot">&lt;-</span> [<span class="op">-</span>height<span class="op">..</span>height]]</a></code></pre></div>
</div>
<p>We need a function which transform an integer value to some color:</p>
<div class="codehighlight">
<div class="sourceCode" id="cb11"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb11-1" title="1">colorFromValue n <span class="ot">=</span></a>
<a class="sourceLine" id="cb11-2" title="2"> <span class="kw">let</span></a>
<a class="sourceLine" id="cb11-3" title="3"><span class="ot"> t ::</span> <span class="dt">Int</span> <span class="ot">-&gt;</span> <span class="dt">GLfloat</span></a>
<a class="sourceLine" id="cb11-4" title="4"> t i <span class="ot">=</span> <span class="fl">0.5</span> <span class="op">+</span> <span class="fl">0.5</span><span class="op">*</span><span class="fu">cos</span>( <span class="fu">fromIntegral</span> i <span class="op">/</span> <span class="dv">10</span> )</a>
<a class="sourceLine" id="cb11-5" title="5"> <span class="kw">in</span></a>
<a class="sourceLine" id="cb11-6" title="6"> <span class="dt">Color3</span> (t n) (t (n<span class="op">+</span><span class="dv">5</span>)) (t (n<span class="op">+</span><span class="dv">10</span>))</a></code></pre></div>
</div>
<p>And now the <code>mandel</code> function. Given two coordinates in pixels, it returns some integer value:</p>
<div class="codehighlight">
<div class="sourceCode" id="cb12"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb12-1" title="1">mandel x y <span class="ot">=</span></a>
<a class="sourceLine" id="cb12-2" title="2"> <span class="kw">let</span> r <span class="ot">=</span> <span class="fl">2.0</span> <span class="op">*</span> x <span class="op">/</span> width</a>
<a class="sourceLine" id="cb12-3" title="3"> i <span class="ot">=</span> <span class="fl">2.0</span> <span class="op">*</span> y <span class="op">/</span> height</a>
<a class="sourceLine" id="cb12-4" title="4"> <span class="kw">in</span></a>
<a class="sourceLine" id="cb12-5" title="5"> f (complex r i) <span class="dv">0</span> <span class="dv">64</span></a></code></pre></div>
</div>
<p>It uses the main Mandelbrot function for each complex \(c\). The Mandelbrot set is the set of complex number \(c\) such that the following sequence does not escape to infinity.</p>
<p>Let us define \(f_c: \)</p>
<p><br /><span class="math display"><em>f</em><sub><em>c</em></sub>(<em>z</em>)=<em>z</em><sup>2</sup>+<em>c</em></span><br /></p>
<p>The sequence is:</p>
<p><br /><span class="math display">0<em>f</em><sub><em>c</em></sub>(0) → <em>f</em><sub><em>c</em></sub>(<em>f</em><sub><em>c</em></sub>(0)) → ⋯ → <em>f</em><sub><em>c</em></sub><sup><em>n</em></sup>(0) → ⋯</span><br /></p>
<p>Of course, instead of trying to test the real limit, we just make a test after a finite number of occurrences.</p>
<div class="codehighlight">
<div class="sourceCode" id="cb13"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb13-1" title="1"><span class="ot">f ::</span> <span class="dt">Complex</span> <span class="ot">-&gt;</span> <span class="dt">Complex</span> <span class="ot">-&gt;</span> <span class="dt">Int</span> <span class="ot">-&gt;</span> <span class="dt">Int</span></a>
<a class="sourceLine" id="cb13-2" title="2">f c z <span class="dv">0</span> <span class="ot">=</span> <span class="dv">0</span></a>
<a class="sourceLine" id="cb13-3" title="3">f c z n <span class="ot">=</span> <span class="kw">if</span> (magnitude z <span class="op">&gt;</span> <span class="dv">2</span> )</a>
<a class="sourceLine" id="cb13-4" title="4"> <span class="kw">then</span> n</a>
<a class="sourceLine" id="cb13-5" title="5"> <span class="kw">else</span> f c ((z<span class="op">*</span>z)<span class="op">+</span>c) (n<span class="op">-</span><span class="dv">1</span>)</a></code></pre></div>
</div>
<p>Well, if you download this file (look at the bottom of this section), compile it and run it this is the result:</p>
<div>
<img src="../../../../Scratch/img/blog/Haskell-OpenGL-Mandelbrot/hglmandel_v01.png" alt="The mandelbrot set version 1" />
</div>
<p>A first very interesting property of this program is that the computation for all the points is done only once. It is a bit long before the first image appears, but if you resize the window, it updates instantaneously. This property is a direct consequence of purity. If you look closely, you see that <code>allPoints</code> is a pure list. Therefore, calling <code>allPoints</code> will always render the same result and Haskell is clever enough to use this property. While Haskell doesnt garbage collect <code>allPoints</code> the result is reused for free. We did not specified this value should be saved for later use. It is saved for us.</p>
<p>See what occurs if we make the window bigger:</p>
<div>
<img src="../../../../Scratch/img/blog/Haskell-OpenGL-Mandelbrot/hglmandel_v01_too_wide.png" alt="The mandelbrot too wide, black lines and columns" />
</div>
<p>We see some black lines because we have drawn less point than there is on the surface. We can repair this by drawing little squares instead of just points. But, instead we will do something a bit different and unusual.</p>
<p><a href="code/01_Introduction/hglmandel.lhs" class="cut">Download the source code of this section → 01_Introduction/<strong>hglmandel.lhs</strong> </a></p>
<hr />
<p><a href="code/02_Edges/HGLMandelEdge.lhs" class="cut">Download the source code of this section → 02_Edges/<strong>HGLMandelEdge.lhs</strong></a></p>
<h2 id="only-the-edges">Only the edges</h2>
<div style="display:none">
<div class="codehighlight">
<div class="sourceCode" id="cb14"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb14-1" title="1"><span class="kw">import</span> <span class="dt">Graphics.Rendering.OpenGL</span></a>
<a class="sourceLine" id="cb14-2" title="2"><span class="kw">import</span> <span class="dt">Graphics.UI.GLUT</span></a>
<a class="sourceLine" id="cb14-3" title="3"><span class="kw">import</span> <span class="dt">Data.IORef</span></a>
<a class="sourceLine" id="cb14-4" title="4"><span class="co">-- Use UNPACK data because it is faster</span></a>
<a class="sourceLine" id="cb14-5" title="5"><span class="co">-- The ! is for strict instead of lazy</span></a>
<a class="sourceLine" id="cb14-6" title="6"><span class="kw">data</span> <span class="dt">Complex</span> <span class="ot">=</span> <span class="dt">C</span> <span class="ot">{-# UNPACK #-}</span> <span class="op">!</span><span class="dt">Float</span></a>
<a class="sourceLine" id="cb14-7" title="7"> <span class="ot">{-# UNPACK #-}</span> <span class="op">!</span><span class="dt">Float</span></a>
<a class="sourceLine" id="cb14-8" title="8"> <span class="kw">deriving</span> (<span class="dt">Show</span>,<span class="dt">Eq</span>)</a>
<a class="sourceLine" id="cb14-9" title="9"><span class="kw">instance</span> <span class="dt">Num</span> <span class="dt">Complex</span> <span class="kw">where</span></a>
<a class="sourceLine" id="cb14-10" title="10"> <span class="fu">fromInteger</span> n <span class="ot">=</span> <span class="dt">C</span> (<span class="fu">fromIntegral</span> n) <span class="fl">0.0</span></a>
<a class="sourceLine" id="cb14-11" title="11"> (<span class="dt">C</span> x y) <span class="op">*</span> (<span class="dt">C</span> z t) <span class="ot">=</span> <span class="dt">C</span> (z<span class="op">*</span>x <span class="op">-</span> y<span class="op">*</span>t) (y<span class="op">*</span>z <span class="op">+</span> x<span class="op">*</span>t)</a>
<a class="sourceLine" id="cb14-12" title="12"> (<span class="dt">C</span> x y) <span class="op">+</span> (<span class="dt">C</span> z t) <span class="ot">=</span> <span class="dt">C</span> (x<span class="op">+</span>z) (y<span class="op">+</span>t)</a>
<a class="sourceLine" id="cb14-13" title="13"> <span class="fu">abs</span> (<span class="dt">C</span> x y) <span class="ot">=</span> <span class="dt">C</span> (<span class="fu">sqrt</span> (x<span class="op">*</span>x <span class="op">+</span> y<span class="op">*</span>y)) <span class="fl">0.0</span></a>
<a class="sourceLine" id="cb14-14" title="14"> <span class="fu">signum</span> (<span class="dt">C</span> x y) <span class="ot">=</span> <span class="dt">C</span> (<span class="fu">signum</span> x) <span class="fl">0.0</span></a>
<a class="sourceLine" id="cb14-15" title="15"><span class="ot">complex ::</span> <span class="dt">Float</span> <span class="ot">-&gt;</span> <span class="dt">Float</span> <span class="ot">-&gt;</span> <span class="dt">Complex</span></a>
<a class="sourceLine" id="cb14-16" title="16">complex x y <span class="ot">=</span> <span class="dt">C</span> x y</a>
<a class="sourceLine" id="cb14-17" title="17"></a>
<a class="sourceLine" id="cb14-18" title="18"><span class="ot">real ::</span> <span class="dt">Complex</span> <span class="ot">-&gt;</span> <span class="dt">Float</span></a>
<a class="sourceLine" id="cb14-19" title="19">real (<span class="dt">C</span> x y) <span class="ot">=</span> x</a>
<a class="sourceLine" id="cb14-20" title="20"></a>
<a class="sourceLine" id="cb14-21" title="21"><span class="ot">im ::</span> <span class="dt">Complex</span> <span class="ot">-&gt;</span> <span class="dt">Float</span></a>
<a class="sourceLine" id="cb14-22" title="22">im (<span class="dt">C</span> x y) <span class="ot">=</span> y</a>
<a class="sourceLine" id="cb14-23" title="23"></a>
<a class="sourceLine" id="cb14-24" title="24"><span class="ot">magnitude ::</span> <span class="dt">Complex</span> <span class="ot">-&gt;</span> <span class="dt">Float</span></a>
<a class="sourceLine" id="cb14-25" title="25">magnitude <span class="ot">=</span> real<span class="op">.</span><span class="fu">abs</span></a>
<a class="sourceLine" id="cb14-26" title="26"><span class="ot">main ::</span> <span class="dt">IO</span> ()</a>
<a class="sourceLine" id="cb14-27" title="27">main <span class="ot">=</span> <span class="kw">do</span></a>
<a class="sourceLine" id="cb14-28" title="28"> <span class="co">-- GLUT need to be initialized</span></a>
<a class="sourceLine" id="cb14-29" title="29"> (progname,_) <span class="ot">&lt;-</span> getArgsAndInitialize</a>
<a class="sourceLine" id="cb14-30" title="30"> <span class="co">-- We will use the double buffered mode (GL constraint)</span></a>
<a class="sourceLine" id="cb14-31" title="31"> initialDisplayMode <span class="op">$=</span> [<span class="dt">DoubleBuffered</span>]</a>
<a class="sourceLine" id="cb14-32" title="32"> <span class="co">-- We create a window with some title</span></a>
<a class="sourceLine" id="cb14-33" title="33"> createWindow <span class="st">&quot;Mandelbrot Set with Haskell and OpenGL&quot;</span></a>
<a class="sourceLine" id="cb14-34" title="34"> <span class="co">-- Each time we will need to update the display</span></a>
<a class="sourceLine" id="cb14-35" title="35"> <span class="co">-- we will call the function 'display'</span></a>
<a class="sourceLine" id="cb14-36" title="36"> displayCallback <span class="op">$=</span> display</a>
<a class="sourceLine" id="cb14-37" title="37"> <span class="co">-- We enter the main loop</span></a>
<a class="sourceLine" id="cb14-38" title="38"> mainLoop</a>
<a class="sourceLine" id="cb14-39" title="39">display <span class="ot">=</span> <span class="kw">do</span></a>
<a class="sourceLine" id="cb14-40" title="40"> <span class="co">-- set the background color (dark solarized theme)</span></a>
<a class="sourceLine" id="cb14-41" title="41"> clearColor <span class="op">$=</span> <span class="dt">Color4</span> <span class="dv">0</span> <span class="fl">0.1686</span> <span class="fl">0.2117</span> <span class="dv">1</span></a>
<a class="sourceLine" id="cb14-42" title="42"> clear [<span class="dt">ColorBuffer</span>] <span class="co">-- make the window black</span></a>
<a class="sourceLine" id="cb14-43" title="43"> loadIdentity <span class="co">-- reset any transformation</span></a>
<a class="sourceLine" id="cb14-44" title="44"> preservingMatrix drawMandelbrot</a>
<a class="sourceLine" id="cb14-45" title="45"> swapBuffers <span class="co">-- refresh screen</span></a>
<a class="sourceLine" id="cb14-46" title="46"></a>
<a class="sourceLine" id="cb14-47" title="47">width <span class="ot">=</span> <span class="dv">320</span><span class="ot"> ::</span> <span class="dt">GLfloat</span></a>
<a class="sourceLine" id="cb14-48" title="48">height <span class="ot">=</span> <span class="dv">320</span><span class="ot"> ::</span> <span class="dt">GLfloat</span></a></code></pre></div>
</div>
</div>
<p>This time, instead of drawing all points, we will simply draw the edges of the Mandelbrot set. The method I use is a rough approximation. I consider the Mandelbrot set to be almost convex. The result will be good enough for the purpose of this tutorial.</p>
<p>We change slightly the <code>drawMandelbrot</code> function. We replace the <code>Points</code> by <code>LineLoop</code></p>
<div class="codehighlight">
<div class="sourceCode" id="cb15"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb15-1" title="1">drawMandelbrot <span class="ot">=</span></a>
<a class="sourceLine" id="cb15-2" title="2"> <span class="co">-- We will print Points (not triangles for example)</span></a>
<a class="sourceLine" id="cb15-3" title="3"> renderPrimitive <span class="dt">LineLoop</span> <span class="op">$</span> <span class="kw">do</span></a>
<a class="sourceLine" id="cb15-4" title="4"> <span class="fu">mapM_</span> drawColoredPoint allPoints</a>
<a class="sourceLine" id="cb15-5" title="5"> <span class="kw">where</span></a>
<a class="sourceLine" id="cb15-6" title="6"> drawColoredPoint (x,y,c) <span class="ot">=</span> <span class="kw">do</span></a>
<a class="sourceLine" id="cb15-7" title="7"> color c <span class="co">-- set the current color to c</span></a>
<a class="sourceLine" id="cb15-8" title="8"> <span class="co">-- then draw the point at position (x,y,0)</span></a>
<a class="sourceLine" id="cb15-9" title="9"> <span class="co">-- remember we're in 3D</span></a>
<a class="sourceLine" id="cb15-10" title="10"> vertex <span class="op">$</span> <span class="dt">Vertex3</span> x y <span class="dv">0</span></a></code></pre></div>
</div>
<p>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.</p>
<div class="codehighlight">
<div class="sourceCode" id="cb16"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb16-1" title="1">allPoints <span class="ot">=</span> positivePoints <span class="op">++</span></a>
<a class="sourceLine" id="cb16-2" title="2"> <span class="fu">map</span> (\(x,y,c) <span class="ot">-&gt;</span> (x,<span class="op">-</span>y,c)) (<span class="fu">reverse</span> positivePoints)</a></code></pre></div>
</div>
<p>We only need to compute the positive point. The Mandelbrot set is symmetric relatively to the abscisse axis.</p>
<div class="codehighlight">
<div class="sourceCode" id="cb17"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb17-1" title="1"><span class="ot">positivePoints ::</span> [(<span class="dt">GLfloat</span>,<span class="dt">GLfloat</span>,<span class="dt">Color3</span> <span class="dt">GLfloat</span>)]</a>
<a class="sourceLine" id="cb17-2" title="2">positivePoints <span class="ot">=</span> <span class="kw">do</span></a>
<a class="sourceLine" id="cb17-3" title="3"> x <span class="ot">&lt;-</span> [<span class="op">-</span>width<span class="op">..</span>width]</a>
<a class="sourceLine" id="cb17-4" title="4"> <span class="kw">let</span> y <span class="ot">=</span> maxZeroIndex (mandel x) <span class="dv">0</span> height (log2 height)</a>
<a class="sourceLine" id="cb17-5" title="5"> <span class="kw">if</span> y <span class="op">&lt;</span> <span class="dv">1</span> <span class="co">-- We don't draw point in the absciss</span></a>
<a class="sourceLine" id="cb17-6" title="6"> <span class="kw">then</span> []</a>
<a class="sourceLine" id="cb17-7" title="7"> <span class="kw">else</span> <span class="fu">return</span> (x<span class="op">/</span>width,y<span class="op">/</span>height,colorFromValue <span class="op">$</span> mandel x y)</a>
<a class="sourceLine" id="cb17-8" title="8"> <span class="kw">where</span></a>
<a class="sourceLine" id="cb17-9" title="9"> log2 n <span class="ot">=</span> <span class="fu">floor</span> ((<span class="fu">log</span> n) <span class="op">/</span> <span class="fu">log</span> <span class="dv">2</span>)</a></code></pre></div>
</div>
<p>This function is interesting. For those not used to the list monad here is a natural language version of this function:</p>
<pre class="no-highlight"><code>positivePoints =
for all x in the range [-width..width]
let y be smallest number s.t. mandel x y &gt; 0
if y is on 0 then don't return a point
else return the value corresonding to (x,y,color for (x+iy))</code></pre>
<p>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. To find the smallest number such that <code>mandel x y &gt; 0</code> we use a simple dichotomy:</p>
<div class="codehighlight">
<div class="sourceCode" id="cb19"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb19-1" title="1"><span class="co">-- given f min max nbtest,</span></a>
<a class="sourceLine" id="cb19-2" title="2"><span class="co">-- considering</span></a>
<a class="sourceLine" id="cb19-3" title="3"><span class="co">-- - f is an increasing function</span></a>
<a class="sourceLine" id="cb19-4" title="4"><span class="co">-- - f(min)=0</span></a>
<a class="sourceLine" id="cb19-5" title="5"><span class="co">-- - f(max)≠0</span></a>
<a class="sourceLine" id="cb19-6" title="6"><span class="co">-- then maxZeroIndex f min max nbtest returns x such that</span></a>
<a class="sourceLine" id="cb19-7" title="7"><span class="co">-- f(x - ε)=0 and f(x + ε)≠0</span></a>
<a class="sourceLine" id="cb19-8" title="8"><span class="co">-- where ε=(max-min)/2^(nbtest+1)</span></a>
<a class="sourceLine" id="cb19-9" title="9">maxZeroIndex func minval maxval <span class="dv">0</span> <span class="ot">=</span> (minval<span class="op">+</span>maxval)<span class="op">/</span><span class="dv">2</span></a>
<a class="sourceLine" id="cb19-10" title="10">maxZeroIndex func minval maxval n <span class="ot">=</span></a>
<a class="sourceLine" id="cb19-11" title="11"> <span class="kw">if</span> (func medpoint) <span class="op">/=</span> <span class="dv">0</span></a>
<a class="sourceLine" id="cb19-12" title="12"> <span class="kw">then</span> maxZeroIndex func minval medpoint (n<span class="op">-</span><span class="dv">1</span>)</a>
<a class="sourceLine" id="cb19-13" title="13"> <span class="kw">else</span> maxZeroIndex func medpoint maxval (n<span class="op">-</span><span class="dv">1</span>)</a>
<a class="sourceLine" id="cb19-14" title="14"> <span class="kw">where</span> medpoint <span class="ot">=</span> (minval<span class="op">+</span>maxval)<span class="op">/</span><span class="dv">2</span></a></code></pre></div>
</div>
<p>No rocket science here. See the result now:</p>
<div>
<img src="../../../../Scratch/img/blog/Haskell-OpenGL-Mandelbrot/HGLMandelEdges.png" alt="The edges of the mandelbrot set" />
</div>
<div style="display:none">
<div class="codehighlight">
<div class="sourceCode" id="cb20"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb20-1" title="1">colorFromValue n <span class="ot">=</span></a>
<a class="sourceLine" id="cb20-2" title="2"> <span class="kw">let</span></a>
<a class="sourceLine" id="cb20-3" title="3"><span class="ot"> t ::</span> <span class="dt">Int</span> <span class="ot">-&gt;</span> <span class="dt">GLfloat</span></a>
<a class="sourceLine" id="cb20-4" title="4"> t i <span class="ot">=</span> <span class="fl">0.5</span> <span class="op">+</span> <span class="fl">0.5</span><span class="op">*</span><span class="fu">cos</span>( <span class="fu">fromIntegral</span> i <span class="op">/</span> <span class="dv">10</span> )</a>
<a class="sourceLine" id="cb20-5" title="5"> <span class="kw">in</span></a>
<a class="sourceLine" id="cb20-6" title="6"> <span class="dt">Color3</span> (t n) (t (n<span class="op">+</span><span class="dv">5</span>)) (t (n<span class="op">+</span><span class="dv">10</span>))</a></code></pre></div>
</div>
<div class="codehighlight">
<div class="sourceCode" id="cb21"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb21-1" title="1">mandel x y <span class="ot">=</span></a>
<a class="sourceLine" id="cb21-2" title="2"> <span class="kw">let</span> r <span class="ot">=</span> <span class="fl">2.0</span> <span class="op">*</span> x <span class="op">/</span> width</a>
<a class="sourceLine" id="cb21-3" title="3"> i <span class="ot">=</span> <span class="fl">2.0</span> <span class="op">*</span> y <span class="op">/</span> height</a>
<a class="sourceLine" id="cb21-4" title="4"> <span class="kw">in</span></a>
<a class="sourceLine" id="cb21-5" title="5"> f (complex r i) <span class="dv">0</span> <span class="dv">64</span></a></code></pre></div>
</div>
<div class="codehighlight">
<div class="sourceCode" id="cb22"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb22-1" title="1"><span class="ot">f ::</span> <span class="dt">Complex</span> <span class="ot">-&gt;</span> <span class="dt">Complex</span> <span class="ot">-&gt;</span> <span class="dt">Int</span> <span class="ot">-&gt;</span> <span class="dt">Int</span></a>
<a class="sourceLine" id="cb22-2" title="2">f c z <span class="dv">0</span> <span class="ot">=</span> <span class="dv">0</span></a>
<a class="sourceLine" id="cb22-3" title="3">f c z n <span class="ot">=</span> <span class="kw">if</span> (magnitude z <span class="op">&gt;</span> <span class="dv">2</span> )</a>
<a class="sourceLine" id="cb22-4" title="4"> <span class="kw">then</span> n</a>
<a class="sourceLine" id="cb22-5" title="5"> <span class="kw">else</span> f c ((z<span class="op">*</span>z)<span class="op">+</span>c) (n<span class="op">-</span><span class="dv">1</span>)</a></code></pre></div>
</div>
</div>
<p><a href="code/02_Edges/HGLMandelEdge.lhs" class="cut">Download the source code of this section → 02_Edges/<strong>HGLMandelEdge.lhs</strong> </a></p>
<hr />
<p><a href="code/03_Mandelbulb/Mandelbulb.lhs" class="cut">Download the source code of this section → 03_Mandelbulb/<strong>Mandelbulb.lhs</strong></a></p>
<h2 id="d-mandelbrot">3D Mandelbrot?</h2>
<p>Now we will we extend to a third dimension. But, there is no 3D equivalent to complex. In fact, the only extension known are quaternions (in 4D). As I know almost nothing about quaternions, I will use some extended complex, instead of using a 3D projection of quaternions. I am pretty sure this construction is not useful for numbers. But it will be enough for us to create something that look nice.</p>
<p>This section is quite long, but dont be afraid, most of the code is some OpenGL boilerplate. If you just want to skim this section, here is a high level representation:</p>
<blockquote>
<ul>
<li>OpenGL Boilerplate
<ul>
<li>set some <code>IORef</code> (understand variables) for states</li>
<li>Drawing:
<ul>
<li>set doubleBuffer, handle depth, window size…</li>
<li>Use state to apply some transformations</li>
</ul></li>
<li>Keyboard: hitting some key change the state of <code>IORef</code></li>
</ul></li>
<li>Generate 3D Object
<div>
</div>
<div class="sourceCode" id="cb23"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb23-1" title="1"><span class="ot">allPoints ::</span> [<span class="dt">ColoredPoint</span>]</a>
<a class="sourceLine" id="cb23-2" title="2">allPoints <span class="ot">=</span></a>
<a class="sourceLine" id="cb23-3" title="3"> for <span class="fu">all</span> (x,y), <span class="op">-</span>width<span class="op">&lt;</span>x<span class="op">&lt;</span>width, <span class="dv">0</span><span class="op">&lt;</span>y<span class="op">&lt;</span>height</a>
<a class="sourceLine" id="cb23-4" title="4"> <span class="dt">Let</span> z be the minimal depth such that</a>
<a class="sourceLine" id="cb23-5" title="5"> mandel x y z <span class="op">&gt;</span> <span class="dv">0</span></a>
<a class="sourceLine" id="cb23-6" title="6"> add the points</a>
<a class="sourceLine" id="cb23-7" title="7"> (x, y, z,color)</a>
<a class="sourceLine" id="cb23-8" title="8"> (x,<span class="op">-</span>y, z,color)</a>
<a class="sourceLine" id="cb23-9" title="9"> (x, y,<span class="op">-</span>z,color)</a>
<a class="sourceLine" id="cb23-10" title="10"> (x,<span class="op">-</span>y,<span class="op">-</span>z,color)</a>
<a class="sourceLine" id="cb23-11" title="11"> <span class="op">+</span> neighbors to make triangles</a></code></pre></div></li>
</ul>
</blockquote>
<div style="display:none">
<div class="codehighlight">
<div class="sourceCode" id="cb24"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb24-1" title="1"><span class="kw">import</span> <span class="dt">Graphics.Rendering.OpenGL</span></a>
<a class="sourceLine" id="cb24-2" title="2"><span class="kw">import</span> <span class="dt">Graphics.UI.GLUT</span></a>
<a class="sourceLine" id="cb24-3" title="3"><span class="kw">import</span> <span class="dt">Data.IORef</span></a>
<a class="sourceLine" id="cb24-4" title="4"><span class="kw">type</span> <span class="dt">ColoredPoint</span> <span class="ot">=</span> (<span class="dt">GLfloat</span>,<span class="dt">GLfloat</span>,<span class="dt">GLfloat</span>,<span class="dt">Color3</span> <span class="dt">GLfloat</span>)</a></code></pre></div>
</div>
</div>
<p>We declare a new type <code>ExtComplex</code> (for extended complex). An extension of complex numbers with a third component:</p>
<div class="codehighlight">
<div class="sourceCode" id="cb25"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb25-1" title="1"><span class="kw">data</span> <span class="dt">ExtComplex</span> <span class="ot">=</span> <span class="dt">C</span> <span class="dt">GLfloat</span> <span class="dt">GLfloat</span> <span class="dt">GLfloat</span></a>
<a class="sourceLine" id="cb25-2" title="2"> <span class="kw">deriving</span> (<span class="dt">Show</span>,<span class="dt">Eq</span>)</a>
<a class="sourceLine" id="cb25-3" title="3"><span class="kw">instance</span> <span class="dt">Num</span> <span class="dt">ExtComplex</span> <span class="kw">where</span></a>
<a class="sourceLine" id="cb25-4" title="4"> <span class="co">-- The shape of the 3D mandelbrot</span></a>
<a class="sourceLine" id="cb25-5" title="5"> <span class="co">-- will depend on this formula</span></a>
<a class="sourceLine" id="cb25-6" title="6"> (<span class="dt">C</span> x y z) <span class="op">*</span> (<span class="dt">C</span> x' y' z') <span class="ot">=</span> <span class="dt">C</span> (x<span class="op">*</span>x' <span class="op">-</span> y<span class="op">*</span>y' <span class="op">-</span> z<span class="op">*</span>z')</a>
<a class="sourceLine" id="cb25-7" title="7"> (x<span class="op">*</span>y' <span class="op">+</span> y<span class="op">*</span>x' <span class="op">+</span> z<span class="op">*</span>z')</a>
<a class="sourceLine" id="cb25-8" title="8"> (x<span class="op">*</span>z' <span class="op">+</span> z<span class="op">*</span>x')</a>
<a class="sourceLine" id="cb25-9" title="9"> <span class="co">-- The rest is straightforward</span></a>
<a class="sourceLine" id="cb25-10" title="10"> <span class="fu">fromInteger</span> n <span class="ot">=</span> <span class="dt">C</span> (<span class="fu">fromIntegral</span> n) <span class="dv">0</span> <span class="dv">0</span></a>
<a class="sourceLine" id="cb25-11" title="11"> (<span class="dt">C</span> x y z) <span class="op">+</span> (<span class="dt">C</span> x' y' z') <span class="ot">=</span> <span class="dt">C</span> (x<span class="op">+</span>x') (y<span class="op">+</span>y') (z<span class="op">+</span>z')</a>
<a class="sourceLine" id="cb25-12" title="12"> <span class="fu">abs</span> (<span class="dt">C</span> x y z) <span class="ot">=</span> <span class="dt">C</span> (<span class="fu">sqrt</span> (x<span class="op">*</span>x <span class="op">+</span> y<span class="op">*</span>y <span class="op">+</span> z<span class="op">*</span>z)) <span class="dv">0</span> <span class="dv">0</span></a>
<a class="sourceLine" id="cb25-13" title="13"> <span class="fu">signum</span> (<span class="dt">C</span> x y z) <span class="ot">=</span> <span class="dt">C</span> (<span class="fu">signum</span> x) (<span class="fu">signum</span> y) (<span class="fu">signum</span> z)</a></code></pre></div>
</div>
<p>The most important part is the new multiplication instance. Modifying this formula will change radically the shape of the result. Here is the formula written in a more mathematical notation. I called the third component of these extended complex <em>strange</em>.</p>
<p><br /><span class="math display"><em>r</em><em>e</em><em>a</em><em>l</em>((<em>x</em>,<em>y</em>,<em>z</em>)×(<em>x</em>,<em>y</em>,<em>z</em>))=<em>x</em><em>x</em><em>y</em><em>y</em><em>z</em><em>z</em></span><br /></p>
<p><br /><span class="math display"><em>i</em><em>m</em>((<em>x</em>,<em>y</em>,<em>z</em>)×(<em>x</em>,<em>y</em>,<em>z</em>))=<em>x</em><em>y</em><em>y</em><em>x</em>+<em>z</em><em>z</em></span><br /></p>
<p><br /><span class="math display"><em>s</em><em>t</em><em>r</em><em>a</em><em>n</em><em>g</em><em>e</em>((<em>x</em>,<em>y</em>,<em>z</em>)×(<em>x</em>,<em>y</em>,<em>z</em>))=<em>x</em><em>z</em>+<em>z</em><em>x</em></span><br /></p>
<p>Note how if \(z=z=0\) then the multiplication is the same to the complex one.</p>
<div style="display:none">
<div class="codehighlight">
<div class="sourceCode" id="cb26"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb26-1" title="1"><span class="ot">extcomplex ::</span> <span class="dt">GLfloat</span> <span class="ot">-&gt;</span> <span class="dt">GLfloat</span> <span class="ot">-&gt;</span> <span class="dt">GLfloat</span> <span class="ot">-&gt;</span> <span class="dt">ExtComplex</span></a>
<a class="sourceLine" id="cb26-2" title="2">extcomplex x y z <span class="ot">=</span> <span class="dt">C</span> x y z</a>
<a class="sourceLine" id="cb26-3" title="3"></a>
<a class="sourceLine" id="cb26-4" title="4"><span class="ot">real ::</span> <span class="dt">ExtComplex</span> <span class="ot">-&gt;</span> <span class="dt">GLfloat</span></a>
<a class="sourceLine" id="cb26-5" title="5">real (<span class="dt">C</span> (x,y,z)) <span class="ot">=</span> x</a>
<a class="sourceLine" id="cb26-6" title="6"></a>
<a class="sourceLine" id="cb26-7" title="7"><span class="ot">im ::</span> <span class="dt">ExtComplex</span> <span class="ot">-&gt;</span> <span class="dt">GLfloat</span></a>
<a class="sourceLine" id="cb26-8" title="8">im (<span class="dt">C</span> (x,y,z)) <span class="ot">=</span> y</a>
<a class="sourceLine" id="cb26-9" title="9"></a>
<a class="sourceLine" id="cb26-10" title="10"><span class="ot">strange ::</span> <span class="dt">ExtComplex</span> <span class="ot">-&gt;</span> <span class="dt">GLfloat</span></a>
<a class="sourceLine" id="cb26-11" title="11">strange (<span class="dt">C</span> (x,y,z)) <span class="ot">=</span> z</a>
<a class="sourceLine" id="cb26-12" title="12"></a>
<a class="sourceLine" id="cb26-13" title="13"><span class="ot">magnitude ::</span> <span class="dt">ExtComplex</span> <span class="ot">-&gt;</span> <span class="dt">GLfloat</span></a>
<a class="sourceLine" id="cb26-14" title="14">magnitude <span class="ot">=</span> real<span class="op">.</span><span class="fu">abs</span></a></code></pre></div>
</div>
</div>
<h3 id="from-2d-to-3d">From 2D to 3D</h3>
<p>As we will use some 3D, we add some new directive in the boilerplate. But mainly, we simply state that will use some depth buffer. And also we will listen the keyboard.</p>
<div class="codehighlight">
<div class="sourceCode" id="cb27"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb27-1" title="1"><span class="ot">main ::</span> <span class="dt">IO</span> ()</a>
<a class="sourceLine" id="cb27-2" title="2">main <span class="ot">=</span> <span class="kw">do</span></a>
<a class="sourceLine" id="cb27-3" title="3"> <span class="co">-- GLUT need to be initialized</span></a>
<a class="sourceLine" id="cb27-4" title="4"> (progname,_) <span class="ot">&lt;-</span> getArgsAndInitialize</a>
<a class="sourceLine" id="cb27-5" title="5"> <span class="co">-- We will use the double buffered mode (GL constraint)</span></a>
<a class="sourceLine" id="cb27-6" title="6"> <span class="co">-- We also Add the DepthBuffer (for 3D)</span></a>
<a class="sourceLine" id="cb27-7" title="7"> initialDisplayMode <span class="op">$=</span></a>
<a class="sourceLine" id="cb27-8" title="8"> [<span class="dt">WithDepthBuffer</span>,<span class="dt">DoubleBuffered</span>,<span class="dt">RGBMode</span>]</a>
<a class="sourceLine" id="cb27-9" title="9"> <span class="co">-- We create a window with some title</span></a>
<a class="sourceLine" id="cb27-10" title="10"> createWindow <span class="st">&quot;3D HOpengGL Mandelbrot&quot;</span></a>
<a class="sourceLine" id="cb27-11" title="11"> <span class="co">-- We add some directives</span></a>
<a class="sourceLine" id="cb27-12" title="12"> depthFunc <span class="op">$=</span> <span class="dt">Just</span> <span class="dt">Less</span></a>
<a class="sourceLine" id="cb27-13" title="13"> windowSize <span class="op">$=</span> <span class="dt">Size</span> <span class="dv">500</span> <span class="dv">500</span></a>
<a class="sourceLine" id="cb27-14" title="14"> <span class="co">-- Some state variables (I know it feels BAD)</span></a>
<a class="sourceLine" id="cb27-15" title="15"> angle <span class="ot">&lt;-</span> newIORef ((<span class="dv">35</span>,<span class="dv">0</span>)<span class="ot">::</span>(<span class="dt">GLfloat</span>,<span class="dt">GLfloat</span>))</a>
<a class="sourceLine" id="cb27-16" title="16"> zoom <span class="ot">&lt;-</span> newIORef (<span class="dv">2</span><span class="ot">::</span><span class="dt">GLfloat</span>)</a>
<a class="sourceLine" id="cb27-17" title="17"> campos <span class="ot">&lt;-</span> newIORef ((<span class="fl">0.7</span>,<span class="dv">0</span>)<span class="ot">::</span>(<span class="dt">GLfloat</span>,<span class="dt">GLfloat</span>))</a>
<a class="sourceLine" id="cb27-18" title="18"> <span class="co">-- Function to call each frame</span></a>
<a class="sourceLine" id="cb27-19" title="19"> idleCallback <span class="op">$=</span> <span class="dt">Just</span> idle</a>
<a class="sourceLine" id="cb27-20" title="20"> <span class="co">-- Function to call when keyboard or mouse is used</span></a>
<a class="sourceLine" id="cb27-21" title="21"> keyboardMouseCallback <span class="op">$=</span></a>
<a class="sourceLine" id="cb27-22" title="22"> <span class="dt">Just</span> (keyboardMouse angle zoom campos)</a>
<a class="sourceLine" id="cb27-23" title="23"> <span class="co">-- Each time we will need to update the display</span></a>
<a class="sourceLine" id="cb27-24" title="24"> <span class="co">-- we will call the function 'display'</span></a>
<a class="sourceLine" id="cb27-25" title="25"> <span class="co">-- But this time, we add some parameters</span></a>
<a class="sourceLine" id="cb27-26" title="26"> displayCallback <span class="op">$=</span> display angle zoom campos</a>
<a class="sourceLine" id="cb27-27" title="27"> <span class="co">-- We enter the main loop</span></a>
<a class="sourceLine" id="cb27-28" title="28"> mainLoop</a></code></pre></div>
</div>
<p>The <code>idle</code> is here to change the states. There should never be any modification done in the <code>display</code> function.</p>
<div class="codehighlight">
<div class="sourceCode" id="cb28"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb28-1" title="1">idle <span class="ot">=</span> postRedisplay <span class="dt">Nothing</span></a></code></pre></div>
</div>
<p>We introduce some helper function to manipulate standard <code>IORef</code>. Mainly <code>modVar x f</code> is equivalent to the imperative <code>x:=f(x)</code>, <code>modFst (x,y) (+1)</code> is equivalent to <code>(x,y) := (x+1,y)</code> and <code>modSnd (x,y) (+1)</code> is equivalent to <code>(x,y) := (x,y+1)</code></p>
<div class="codehighlight">
<div class="sourceCode" id="cb29"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb29-1" title="1">modVar v f <span class="ot">=</span> <span class="kw">do</span></a>
<a class="sourceLine" id="cb29-2" title="2"> v' <span class="ot">&lt;-</span> get v</a>
<a class="sourceLine" id="cb29-3" title="3"> v <span class="op">$=</span> (f v')</a>
<a class="sourceLine" id="cb29-4" title="4">mapFst f (x,y) <span class="ot">=</span> (f x, y)</a>
<a class="sourceLine" id="cb29-5" title="5">mapSnd f (x,y) <span class="ot">=</span> ( x,f y)</a></code></pre></div>
</div>
<p>And we use them to code the function handling keyboard. We will use the keys <code>hjkl</code> to rotate, <code>oi</code> to zoom and <code>sedf</code> to move. Also, hitting space will reset the view. Remember that <code>angle</code> and <code>campos</code> are pairs and <code>zoom</code> is a scalar. Also note <code>(+0.5)</code> is the function <code>\x-&gt;x+0.5</code> and <code>(-0.5)</code> is the number <code>-0.5</code> (yes I share your pain).</p>
<div class="codehighlight">
<div class="sourceCode" id="cb30"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb30-1" title="1">keyboardMouse angle zoom campos key state modifiers position <span class="ot">=</span></a>
<a class="sourceLine" id="cb30-2" title="2"> <span class="co">-- We won't use modifiers nor position</span></a>
<a class="sourceLine" id="cb30-3" title="3"> kact angle zoom campos key state</a>
<a class="sourceLine" id="cb30-4" title="4"> <span class="kw">where</span></a>
<a class="sourceLine" id="cb30-5" title="5"> <span class="co">-- reset view when hitting space</span></a>
<a class="sourceLine" id="cb30-6" title="6"> kact a z p (<span class="dt">Char</span> <span class="ch">' '</span>) <span class="dt">Down</span> <span class="ot">=</span> <span class="kw">do</span></a>
<a class="sourceLine" id="cb30-7" title="7"> a <span class="op">$=</span> (<span class="dv">0</span>,<span class="dv">0</span>) <span class="co">-- angle</span></a>
<a class="sourceLine" id="cb30-8" title="8"> z <span class="op">$=</span> <span class="dv">1</span> <span class="co">-- zoom</span></a>
<a class="sourceLine" id="cb30-9" title="9"> p <span class="op">$=</span> (<span class="dv">0</span>,<span class="dv">0</span>) <span class="co">-- camera position</span></a>
<a class="sourceLine" id="cb30-10" title="10"> <span class="co">-- use of hjkl to rotate</span></a>
<a class="sourceLine" id="cb30-11" title="11"> kact a _ _ (<span class="dt">Char</span> <span class="ch">'h'</span>) <span class="dt">Down</span> <span class="ot">=</span> modVar a (mapFst (<span class="op">+</span><span class="fl">0.5</span>))</a>
<a class="sourceLine" id="cb30-12" title="12"> kact a _ _ (<span class="dt">Char</span> <span class="ch">'l'</span>) <span class="dt">Down</span> <span class="ot">=</span> modVar a (mapFst (<span class="op">+</span>(<span class="op">-</span><span class="fl">0.5</span>)))</a>
<a class="sourceLine" id="cb30-13" title="13"> kact a _ _ (<span class="dt">Char</span> <span class="ch">'j'</span>) <span class="dt">Down</span> <span class="ot">=</span> modVar a (mapSnd (<span class="op">+</span><span class="fl">0.5</span>))</a>
<a class="sourceLine" id="cb30-14" title="14"> kact a _ _ (<span class="dt">Char</span> <span class="ch">'k'</span>) <span class="dt">Down</span> <span class="ot">=</span> modVar a (mapSnd (<span class="op">+</span>(<span class="op">-</span><span class="fl">0.5</span>)))</a>
<a class="sourceLine" id="cb30-15" title="15"> <span class="co">-- use o and i to zoom</span></a>
<a class="sourceLine" id="cb30-16" title="16"> kact _ z _ (<span class="dt">Char</span> <span class="ch">'o'</span>) <span class="dt">Down</span> <span class="ot">=</span> modVar z (<span class="op">*</span><span class="fl">1.1</span>)</a>
<a class="sourceLine" id="cb30-17" title="17"> kact _ z _ (<span class="dt">Char</span> <span class="ch">'i'</span>) <span class="dt">Down</span> <span class="ot">=</span> modVar z (<span class="op">*</span><span class="fl">0.9</span>)</a>
<a class="sourceLine" id="cb30-18" title="18"> <span class="co">-- use sdfe to move the camera</span></a>
<a class="sourceLine" id="cb30-19" title="19"> kact _ _ p (<span class="dt">Char</span> <span class="ch">'s'</span>) <span class="dt">Down</span> <span class="ot">=</span> modVar p (mapFst (<span class="op">+</span><span class="fl">0.1</span>))</a>
<a class="sourceLine" id="cb30-20" title="20"> kact _ _ p (<span class="dt">Char</span> <span class="ch">'f'</span>) <span class="dt">Down</span> <span class="ot">=</span> modVar p (mapFst (<span class="op">+</span>(<span class="op">-</span><span class="fl">0.1</span>)))</a>
<a class="sourceLine" id="cb30-21" title="21"> kact _ _ p (<span class="dt">Char</span> <span class="ch">'d'</span>) <span class="dt">Down</span> <span class="ot">=</span> modVar p (mapSnd (<span class="op">+</span><span class="fl">0.1</span>))</a>
<a class="sourceLine" id="cb30-22" title="22"> kact _ _ p (<span class="dt">Char</span> <span class="ch">'e'</span>) <span class="dt">Down</span> <span class="ot">=</span> modVar p (mapSnd (<span class="op">+</span>(<span class="op">-</span><span class="fl">0.1</span>)))</a>
<a class="sourceLine" id="cb30-23" title="23"> <span class="co">-- any other keys does nothing</span></a>
<a class="sourceLine" id="cb30-24" title="24"> kact _ _ _ _ _ <span class="ot">=</span> <span class="fu">return</span> ()</a></code></pre></div>
</div>
<p>Note <code>display</code> takes some parameters this time. This function if full of boilerplate:</p>
<div class="codehighlight">
<div class="sourceCode" id="cb31"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb31-1" title="1">display angle zoom position <span class="ot">=</span> <span class="kw">do</span></a>
<a class="sourceLine" id="cb31-2" title="2"> <span class="co">-- set the background color (dark solarized theme)</span></a>
<a class="sourceLine" id="cb31-3" title="3"> clearColor <span class="op">$=</span> <span class="dt">Color4</span> <span class="dv">0</span> <span class="fl">0.1686</span> <span class="fl">0.2117</span> <span class="dv">1</span></a>
<a class="sourceLine" id="cb31-4" title="4"> clear [<span class="dt">ColorBuffer</span>,<span class="dt">DepthBuffer</span>]</a>
<a class="sourceLine" id="cb31-5" title="5"> <span class="co">-- Transformation to change the view</span></a>
<a class="sourceLine" id="cb31-6" title="6"> loadIdentity <span class="co">-- reset any transformation</span></a>
<a class="sourceLine" id="cb31-7" title="7"> <span class="co">-- tranlate</span></a>
<a class="sourceLine" id="cb31-8" title="8"> (x,y) <span class="ot">&lt;-</span> get position</a>
<a class="sourceLine" id="cb31-9" title="9"> translate <span class="op">$</span> <span class="dt">Vector3</span> x y <span class="dv">0</span></a>
<a class="sourceLine" id="cb31-10" title="10"> <span class="co">-- zoom</span></a>
<a class="sourceLine" id="cb31-11" title="11"> z <span class="ot">&lt;-</span> get zoom</a>
<a class="sourceLine" id="cb31-12" title="12"> scale z z z</a>
<a class="sourceLine" id="cb31-13" title="13"> <span class="co">-- rotate</span></a>
<a class="sourceLine" id="cb31-14" title="14"> (xangle,yangle) <span class="ot">&lt;-</span> get angle</a>
<a class="sourceLine" id="cb31-15" title="15"> rotate xangle <span class="op">$</span> <span class="dt">Vector3</span> <span class="fl">1.0</span> <span class="fl">0.0</span> (<span class="fl">0.0</span><span class="ot">::</span><span class="dt">GLfloat</span>)</a>
<a class="sourceLine" id="cb31-16" title="16"> rotate yangle <span class="op">$</span> <span class="dt">Vector3</span> <span class="fl">0.0</span> <span class="fl">1.0</span> (<span class="fl">0.0</span><span class="ot">::</span><span class="dt">GLfloat</span>)</a>
<a class="sourceLine" id="cb31-17" title="17"></a>
<a class="sourceLine" id="cb31-18" title="18"> <span class="co">-- Now that all transformation were made</span></a>
<a class="sourceLine" id="cb31-19" title="19"> <span class="co">-- We create the object(s)</span></a>
<a class="sourceLine" id="cb31-20" title="20"> preservingMatrix drawMandelbrot</a>
<a class="sourceLine" id="cb31-21" title="21"></a>
<a class="sourceLine" id="cb31-22" title="22"> swapBuffers <span class="co">-- refresh screen</span></a></code></pre></div>
</div>
<p>Not much to say about this function. Mainly there are two parts: apply some transformations, draw the object.</p>
<h3 id="the-3d-mandelbrot">The 3D Mandelbrot</h3>
<p>We have finished with the OpenGL section, lets talk about how we generate the 3D points and colors. First, we will set the number of details to 200 pixels in the three dimensions.</p>
<div class="codehighlight">
<div class="sourceCode" id="cb32"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb32-1" title="1">nbDetails <span class="ot">=</span> <span class="dv">200</span><span class="ot"> ::</span> <span class="dt">GLfloat</span></a>
<a class="sourceLine" id="cb32-2" title="2">width <span class="ot">=</span> nbDetails</a>
<a class="sourceLine" id="cb32-3" title="3">height <span class="ot">=</span> nbDetails</a>
<a class="sourceLine" id="cb32-4" title="4">deep <span class="ot">=</span> nbDetails</a></code></pre></div>
</div>
<p>This time, instead of just drawing some line or some group of points, we will show triangles. The function <code>allPoints</code> will provide a multiple of three points. Each three successive point representing the coordinate of each vertex of a triangle.</p>
<div class="codehighlight">
<div class="sourceCode" id="cb33"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb33-1" title="1">drawMandelbrot <span class="ot">=</span> <span class="kw">do</span></a>
<a class="sourceLine" id="cb33-2" title="2"> <span class="co">-- We will print Points (not triangles for example)</span></a>
<a class="sourceLine" id="cb33-3" title="3"> renderPrimitive <span class="dt">Triangles</span> <span class="op">$</span> <span class="kw">do</span></a>
<a class="sourceLine" id="cb33-4" title="4"> <span class="fu">mapM_</span> drawColoredPoint allPoints</a>
<a class="sourceLine" id="cb33-5" title="5"> <span class="kw">where</span></a>
<a class="sourceLine" id="cb33-6" title="6"> drawColoredPoint (x,y,z,c) <span class="ot">=</span> <span class="kw">do</span></a>
<a class="sourceLine" id="cb33-7" title="7"> color c</a>
<a class="sourceLine" id="cb33-8" title="8"> vertex <span class="op">$</span> <span class="dt">Vertex3</span> x y z</a></code></pre></div>
</div>
<p>In fact, we will provide six ordered points. These points will be used to draw two triangles.</p>
<div>
<img src="../../../../Scratch/img/blog/Haskell-OpenGL-Mandelbrot/triangles.png" alt="Explain triangles" />
</div>
<p>The next function is a bit long. Here is an approximative English version:</p>
<pre><code>forall x from -width to width
forall y from -height to height
forall the neighbors of (x,y)
let z be the smalled depth such that (mandel x y z)&gt;0
let c be the color given by mandel x y z
add the point corresponding to (x,y,z,c)</code></pre>
<p>Also, I added a test to hide points too far from the border. In fact, this function show points close to the surface of the modified mandelbrot set. But not the mandelbrot set itself.</p>
<div class="sourceCode" id="cb35"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb35-1" title="1"><span class="ot">depthPoints ::</span> [<span class="dt">ColoredPoint</span>]</a>
<a class="sourceLine" id="cb35-2" title="2">depthPoints <span class="ot">=</span> <span class="kw">do</span></a>
<a class="sourceLine" id="cb35-3" title="3"> x <span class="ot">&lt;-</span> [<span class="op">-</span>width<span class="op">..</span>width]</a>
<a class="sourceLine" id="cb35-4" title="4"> y <span class="ot">&lt;-</span> [<span class="op">-</span>height<span class="op">..</span>height]</a>
<a class="sourceLine" id="cb35-5" title="5"> <span class="kw">let</span></a>
<a class="sourceLine" id="cb35-6" title="6"> depthOf x' y' <span class="ot">=</span> maxZeroIndex (mandel x' y') <span class="dv">0</span> deep logdeep</a>
<a class="sourceLine" id="cb35-7" title="7"> logdeep <span class="ot">=</span> <span class="fu">floor</span> ((<span class="fu">log</span> deep) <span class="op">/</span> <span class="fu">log</span> <span class="dv">2</span>)</a>
<a class="sourceLine" id="cb35-8" title="8"> z1 <span class="ot">=</span> depthOf x y</a>
<a class="sourceLine" id="cb35-9" title="9"> z2 <span class="ot">=</span> depthOf (x<span class="op">+</span><span class="dv">1</span>) y</a>
<a class="sourceLine" id="cb35-10" title="10"> z3 <span class="ot">=</span> depthOf (x<span class="op">+</span><span class="dv">1</span>) (y<span class="op">+</span><span class="dv">1</span>)</a>
<a class="sourceLine" id="cb35-11" title="11"> z4 <span class="ot">=</span> depthOf x (y<span class="op">+</span><span class="dv">1</span>)</a>
<a class="sourceLine" id="cb35-12" title="12"> c1 <span class="ot">=</span> mandel x y (z1<span class="op">+</span><span class="dv">1</span>)</a>
<a class="sourceLine" id="cb35-13" title="13"> c2 <span class="ot">=</span> mandel (x<span class="op">+</span><span class="dv">1</span>) y (z2<span class="op">+</span><span class="dv">1</span>)</a>
<a class="sourceLine" id="cb35-14" title="14"> c3 <span class="ot">=</span> mandel (x<span class="op">+</span><span class="dv">1</span>) (y<span class="op">+</span><span class="dv">1</span>) (z3<span class="op">+</span><span class="dv">1</span>)</a>
<a class="sourceLine" id="cb35-15" title="15"> c4 <span class="ot">=</span> mandel x (y<span class="op">+</span><span class="dv">1</span>) (z4<span class="op">+</span><span class="dv">1</span>)</a>
<a class="sourceLine" id="cb35-16" title="16"> p1 <span class="ot">=</span> ( x <span class="op">/</span>width, y <span class="op">/</span>height, z1<span class="op">/</span>deep, colorFromValue c1)</a>
<a class="sourceLine" id="cb35-17" title="17"> p2 <span class="ot">=</span> ((x<span class="op">+</span><span class="dv">1</span>)<span class="op">/</span>width, y <span class="op">/</span>height, z2<span class="op">/</span>deep, colorFromValue c2)</a>
<a class="sourceLine" id="cb35-18" title="18"> p3 <span class="ot">=</span> ((x<span class="op">+</span><span class="dv">1</span>)<span class="op">/</span>width,(y<span class="op">+</span><span class="dv">1</span>)<span class="op">/</span>height, z3<span class="op">/</span>deep, colorFromValue c3)</a>
<a class="sourceLine" id="cb35-19" title="19"> p4 <span class="ot">=</span> ( x <span class="op">/</span>width,(y<span class="op">+</span><span class="dv">1</span>)<span class="op">/</span>height, z4<span class="op">/</span>deep, colorFromValue c4)</a>
<a class="sourceLine" id="cb35-20" title="20"> <span class="kw">if</span> (<span class="fu">and</span> <span class="op">$</span> <span class="fu">map</span> (<span class="op">&gt;=</span><span class="dv">57</span>) [c1,c2,c3,c4])</a>
<a class="sourceLine" id="cb35-21" title="21"> <span class="kw">then</span> []</a>
<a class="sourceLine" id="cb35-22" title="22"> <span class="kw">else</span> [p1,p2,p3,p1,p3,p4]</a></code></pre></div>
<p>If you look at the function above, you see a lot of common patterns. Haskell is very efficient to make this better. Here is a harder to read but shorter and more generic rewritten function:</p>
<div class="codehighlight">
<div class="sourceCode" id="cb36"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb36-1" title="1"><span class="ot">depthPoints ::</span> [<span class="dt">ColoredPoint</span>]</a>
<a class="sourceLine" id="cb36-2" title="2">depthPoints <span class="ot">=</span> <span class="kw">do</span></a>
<a class="sourceLine" id="cb36-3" title="3"> x <span class="ot">&lt;-</span> [<span class="op">-</span>width<span class="op">..</span>width]</a>
<a class="sourceLine" id="cb36-4" title="4"> y <span class="ot">&lt;-</span> [<span class="op">-</span>height<span class="op">..</span>height]</a>
<a class="sourceLine" id="cb36-5" title="5"> <span class="kw">let</span></a>
<a class="sourceLine" id="cb36-6" title="6"> neighbors <span class="ot">=</span> [(x,y),(x<span class="op">+</span><span class="dv">1</span>,y),(x<span class="op">+</span><span class="dv">1</span>,y<span class="op">+</span><span class="dv">1</span>),(x,y<span class="op">+</span><span class="dv">1</span>)]</a>
<a class="sourceLine" id="cb36-7" title="7"> depthOf (u,v) <span class="ot">=</span> maxZeroIndex (mandel u v) <span class="dv">0</span> deep logdeep</a>
<a class="sourceLine" id="cb36-8" title="8"> logdeep <span class="ot">=</span> <span class="fu">floor</span> ((<span class="fu">log</span> deep) <span class="op">/</span> <span class="fu">log</span> <span class="dv">2</span>)</a>
<a class="sourceLine" id="cb36-9" title="9"> <span class="co">-- zs are 3D points with found depth</span></a>
<a class="sourceLine" id="cb36-10" title="10"> zs <span class="ot">=</span> <span class="fu">map</span> (\(u,v) <span class="ot">-&gt;</span> (u,v,depthOf (u,v))) neighbors</a>
<a class="sourceLine" id="cb36-11" title="11"> <span class="co">-- ts are 3D pixels + mandel value</span></a>
<a class="sourceLine" id="cb36-12" title="12"> ts <span class="ot">=</span> <span class="fu">map</span> (\(u,v,w) <span class="ot">-&gt;</span> (u,v,w,mandel u v (w<span class="op">+</span><span class="dv">1</span>))) zs</a>
<a class="sourceLine" id="cb36-13" title="13"> <span class="co">-- ps are 3D opengl points + color value</span></a>
<a class="sourceLine" id="cb36-14" title="14"> ps <span class="ot">=</span> <span class="fu">map</span> (\(u,v,w,c') <span class="ot">-&gt;</span></a>
<a class="sourceLine" id="cb36-15" title="15"> (u<span class="op">/</span>width,v<span class="op">/</span>height,w<span class="op">/</span>deep,colorFromValue c')) ts</a>
<a class="sourceLine" id="cb36-16" title="16"> <span class="co">-- If the point diverged too fast, don't display it</span></a>
<a class="sourceLine" id="cb36-17" title="17"> <span class="kw">if</span> (<span class="fu">and</span> <span class="op">$</span> <span class="fu">map</span> (\(_,_,_,c) <span class="ot">-&gt;</span> c<span class="op">&gt;=</span><span class="dv">57</span>) ts)</a>
<a class="sourceLine" id="cb36-18" title="18"> <span class="kw">then</span> []</a>
<a class="sourceLine" id="cb36-19" title="19"> <span class="co">-- Draw two triangles</span></a>
<a class="sourceLine" id="cb36-20" title="20"> <span class="kw">else</span> [ps<span class="op">!!</span><span class="dv">0</span>,ps<span class="op">!!</span><span class="dv">1</span>,ps<span class="op">!!</span><span class="dv">2</span>,ps<span class="op">!!</span><span class="dv">0</span>,ps<span class="op">!!</span><span class="dv">2</span>,ps<span class="op">!!</span><span class="dv">3</span>]</a></code></pre></div>
</div>
<p>If you prefer the first version, then just imagine how hard it will be to change the enumeration of the point from (x,y) to (x,z) for example.</p>
<p>Also, we didnt searched for negative values. This modified Mandelbrot is no more symmetric relatively to the plan <code>y=0</code>. But it is symmetric relatively to the plan <code>z=0</code>. Then I mirror these values.</p>
<div class="codehighlight">
<div class="sourceCode" id="cb37"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb37-1" title="1"><span class="ot">allPoints ::</span> [<span class="dt">ColoredPoint</span>]</a>
<a class="sourceLine" id="cb37-2" title="2">allPoints <span class="ot">=</span> planPoints <span class="op">++</span> <span class="fu">map</span> inverseDepth planPoints</a>
<a class="sourceLine" id="cb37-3" title="3"> <span class="kw">where</span></a>
<a class="sourceLine" id="cb37-4" title="4"> planPoints <span class="ot">=</span> depthPoints</a>
<a class="sourceLine" id="cb37-5" title="5"> inverseDepth (x,y,z,c) <span class="ot">=</span> (x,y,<span class="op">-</span>z<span class="op">+</span><span class="dv">1</span><span class="op">/</span>deep,c)</a></code></pre></div>
</div>
<p>The rest of the program is very close to the preceding one.</p>
<div style="display:none">
<div class="codehighlight">
<div class="sourceCode" id="cb38"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb38-1" title="1"><span class="co">-- given f min max nbtest,</span></a>
<a class="sourceLine" id="cb38-2" title="2"><span class="co">-- considering</span></a>
<a class="sourceLine" id="cb38-3" title="3"><span class="co">-- - f is an increasing function</span></a>
<a class="sourceLine" id="cb38-4" title="4"><span class="co">-- - f(min)=0</span></a>
<a class="sourceLine" id="cb38-5" title="5"><span class="co">-- - f(max)≠0</span></a>
<a class="sourceLine" id="cb38-6" title="6"><span class="co">-- then maxZeroIndex f min max nbtest returns x such that</span></a>
<a class="sourceLine" id="cb38-7" title="7"><span class="co">-- f(x - ε)=0 and f(x + ε)≠0</span></a>
<a class="sourceLine" id="cb38-8" title="8"><span class="co">-- where ε=(max-min)/2^(nbtest+1)</span></a>
<a class="sourceLine" id="cb38-9" title="9"><span class="ot">maxZeroIndex ::</span> (<span class="dt">Fractional</span> a,<span class="dt">Num</span> a,<span class="dt">Num</span> b,<span class="dt">Eq</span> b) <span class="ot">=&gt;</span></a>
<a class="sourceLine" id="cb38-10" title="10"> (a <span class="ot">-&gt;</span> b) <span class="ot">-&gt;</span> a <span class="ot">-&gt;</span> a <span class="ot">-&gt;</span> <span class="dt">Int</span> <span class="ot">-&gt;</span> a</a>
<a class="sourceLine" id="cb38-11" title="11">maxZeroIndex func minval maxval <span class="dv">0</span> <span class="ot">=</span> (minval<span class="op">+</span>maxval)<span class="op">/</span><span class="dv">2</span></a>
<a class="sourceLine" id="cb38-12" title="12">maxZeroIndex func minval maxval n <span class="ot">=</span></a>
<a class="sourceLine" id="cb38-13" title="13"> <span class="kw">if</span> (func medpoint) <span class="op">/=</span> <span class="dv">0</span></a>
<a class="sourceLine" id="cb38-14" title="14"> <span class="kw">then</span> maxZeroIndex func minval medpoint (n<span class="op">-</span><span class="dv">1</span>)</a>
<a class="sourceLine" id="cb38-15" title="15"> <span class="kw">else</span> maxZeroIndex func medpoint maxval (n<span class="op">-</span><span class="dv">1</span>)</a>
<a class="sourceLine" id="cb38-16" title="16"> <span class="kw">where</span> medpoint <span class="ot">=</span> (minval<span class="op">+</span>maxval)<span class="op">/</span><span class="dv">2</span></a></code></pre></div>
</div>
<p>I made the color slightly brighter</p>
<div class="codehighlight">
<div class="sourceCode" id="cb39"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb39-1" title="1">colorFromValue n <span class="ot">=</span></a>
<a class="sourceLine" id="cb39-2" title="2"> <span class="kw">let</span></a>
<a class="sourceLine" id="cb39-3" title="3"><span class="ot"> t ::</span> <span class="dt">Int</span> <span class="ot">-&gt;</span> <span class="dt">GLfloat</span></a>
<a class="sourceLine" id="cb39-4" title="4"> t i <span class="ot">=</span> <span class="fl">0.7</span> <span class="op">+</span> <span class="fl">0.3</span><span class="op">*</span><span class="fu">cos</span>( <span class="fu">fromIntegral</span> i <span class="op">/</span> <span class="dv">10</span> )</a>
<a class="sourceLine" id="cb39-5" title="5"> <span class="kw">in</span></a>
<a class="sourceLine" id="cb39-6" title="6"> <span class="dt">Color3</span> (t n) (t (n<span class="op">+</span><span class="dv">5</span>)) (t (n<span class="op">+</span><span class="dv">10</span>))</a></code></pre></div>
</div>
<p>We only changed from <code>Complex</code> to <code>ExtComplex</code> of the main <code>f</code> function.</p>
<div class="codehighlight">
<div class="sourceCode" id="cb40"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb40-1" title="1"><span class="ot">f ::</span> <span class="dt">ExtComplex</span> <span class="ot">-&gt;</span> <span class="dt">ExtComplex</span> <span class="ot">-&gt;</span> <span class="dt">Int</span> <span class="ot">-&gt;</span> <span class="dt">Int</span></a>
<a class="sourceLine" id="cb40-2" title="2">f c z <span class="dv">0</span> <span class="ot">=</span> <span class="dv">0</span></a>
<a class="sourceLine" id="cb40-3" title="3">f c z n <span class="ot">=</span> <span class="kw">if</span> (magnitude z <span class="op">&gt;</span> <span class="dv">2</span> )</a>
<a class="sourceLine" id="cb40-4" title="4"> <span class="kw">then</span> n</a>
<a class="sourceLine" id="cb40-5" title="5"> <span class="kw">else</span> f c ((z<span class="op">*</span>z)<span class="op">+</span>c) (n<span class="op">-</span><span class="dv">1</span>)</a></code></pre></div>
</div>
</div>
<p>We simply add a new dimension to the <code>mandel</code> function and change the type signature of <code>f</code> from <code>Complex</code> to <code>ExtComplex</code>.</p>
<div class="codehighlight">
<div class="sourceCode" id="cb41"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb41-1" title="1">mandel x y z <span class="ot">=</span></a>
<a class="sourceLine" id="cb41-2" title="2"> <span class="kw">let</span> r <span class="ot">=</span> <span class="fl">2.0</span> <span class="op">*</span> x <span class="op">/</span> width</a>
<a class="sourceLine" id="cb41-3" title="3"> i <span class="ot">=</span> <span class="fl">2.0</span> <span class="op">*</span> y <span class="op">/</span> height</a>
<a class="sourceLine" id="cb41-4" title="4"> s <span class="ot">=</span> <span class="fl">2.0</span> <span class="op">*</span> z <span class="op">/</span> deep</a>
<a class="sourceLine" id="cb41-5" title="5"> <span class="kw">in</span></a>
<a class="sourceLine" id="cb41-6" title="6"> f (extcomplex r i s) <span class="dv">0</span> <span class="dv">64</span></a></code></pre></div>
</div>
<p>Here is the result:</p>
<div>
<img src="../../../../Scratch/img/blog/Haskell-OpenGL-Mandelbrot/mandelbrot_3D.png" alt="A 3D mandelbrot like" />
</div>
<p><a href="code/03_Mandelbulb/Mandelbulb.lhs" class="cut">Download the source code of this section → 03_Mandelbulb/<strong>Mandelbulb.lhs</strong> </a></p>
<hr />
<p><a href="code/04_Mandelbulb/Mandelbulb.lhs" class="cut">Download the source code of this section → 04_Mandelbulb/<strong>Mandelbulb.lhs</strong></a></p>
<h2 id="naïve-code-cleaning">Naïve code cleaning</h2>
<p>The first approach to clean the code is to separate the GLUT/OpenGL part from the computation of the shape. Here is the cleaned version of the preceding section. Most boilerplate was put in external files.</p>
<ul>
<li><a href="code/04_Mandelbulb/YBoiler.hs"><code>YBoiler.hs</code></a>, the 3D rendering</li>
<li><a href="code/04_Mandelbulb/Mandel.hs"><code>Mandel</code></a>, the mandel function</li>
<li><a href="code/04_Mandelbulb/ExtComplex.hs"><code>ExtComplex</code></a>, the extended complexes</li>
</ul>
<div class="codehighlight">
<div class="sourceCode" id="cb42"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb42-1" title="1"><span class="kw">import</span> <span class="dt">YBoiler</span> <span class="co">-- Most the OpenGL Boilerplate</span></a>
<a class="sourceLine" id="cb42-2" title="2"><span class="kw">import</span> <span class="dt">Mandel</span> <span class="co">-- The 3D Mandelbrot maths</span></a></code></pre></div>
</div>
<p>The <code>yMainLoop</code> takes two arguments: the title of the window and a function from time to triangles</p>
<div class="codehighlight">
<div class="sourceCode" id="cb43"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb43-1" title="1"><span class="ot">main ::</span> <span class="dt">IO</span> ()</a>
<a class="sourceLine" id="cb43-2" title="2">main <span class="ot">=</span> yMainLoop <span class="st">&quot;3D Mandelbrot&quot;</span> (\_ <span class="ot">-&gt;</span> allPoints)</a></code></pre></div>
</div>
<p>We set some global constant (this is generally bad).</p>
<div class="codehighlight">
<div class="sourceCode" id="cb44"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb44-1" title="1">nbDetails <span class="ot">=</span> <span class="dv">200</span><span class="ot"> ::</span> <span class="dt">GLfloat</span></a>
<a class="sourceLine" id="cb44-2" title="2">width <span class="ot">=</span> nbDetails</a>
<a class="sourceLine" id="cb44-3" title="3">height <span class="ot">=</span> nbDetails</a>
<a class="sourceLine" id="cb44-4" title="4">deep <span class="ot">=</span> nbDetails</a></code></pre></div>
</div>
<p>We then generate colored points from our function. This is similar to the preceding section.</p>
<div class="codehighlight">
<div class="sourceCode" id="cb45"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb45-1" title="1"><span class="ot">allPoints ::</span> [<span class="dt">ColoredPoint</span>]</a>
<a class="sourceLine" id="cb45-2" title="2">allPoints <span class="ot">=</span> planPoints <span class="op">++</span> <span class="fu">map</span> inverseDepth planPoints</a>
<a class="sourceLine" id="cb45-3" title="3"> <span class="kw">where</span></a>
<a class="sourceLine" id="cb45-4" title="4"> planPoints <span class="ot">=</span> depthPoints <span class="op">++</span> <span class="fu">map</span> inverseHeight depthPoints</a>
<a class="sourceLine" id="cb45-5" title="5"> inverseHeight (x,y,z,c) <span class="ot">=</span> (x,<span class="op">-</span>y,z,c)</a>
<a class="sourceLine" id="cb45-6" title="6"> inverseDepth (x,y,z,c) <span class="ot">=</span> (x,y,<span class="op">-</span>z<span class="op">+</span><span class="dv">1</span><span class="op">/</span>deep,c)</a></code></pre></div>
</div>
<div class="codehighlight">
<div class="sourceCode" id="cb46"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb46-1" title="1"><span class="ot">depthPoints ::</span> [<span class="dt">ColoredPoint</span>]</a>
<a class="sourceLine" id="cb46-2" title="2">depthPoints <span class="ot">=</span> <span class="kw">do</span></a>
<a class="sourceLine" id="cb46-3" title="3"> x <span class="ot">&lt;-</span> [<span class="op">-</span>width<span class="op">..</span>width]</a>
<a class="sourceLine" id="cb46-4" title="4"> y <span class="ot">&lt;-</span> [<span class="dv">0</span><span class="op">..</span>height]</a>
<a class="sourceLine" id="cb46-5" title="5"> <span class="kw">let</span></a>
<a class="sourceLine" id="cb46-6" title="6"> neighbors <span class="ot">=</span> [(x,y),(x<span class="op">+</span><span class="dv">1</span>,y),(x<span class="op">+</span><span class="dv">1</span>,y<span class="op">+</span><span class="dv">1</span>),(x,y<span class="op">+</span><span class="dv">1</span>)]</a>
<a class="sourceLine" id="cb46-7" title="7"> depthOf (u,v) <span class="ot">=</span> maxZeroIndex (ymandel u v) <span class="dv">0</span> deep <span class="dv">7</span></a>
<a class="sourceLine" id="cb46-8" title="8"> <span class="co">-- zs are 3D points with found depth</span></a>
<a class="sourceLine" id="cb46-9" title="9"> zs <span class="ot">=</span> <span class="fu">map</span> (\(u,v) <span class="ot">-&gt;</span> (u,v,depthOf (u,v))) neighbors</a>
<a class="sourceLine" id="cb46-10" title="10"> <span class="co">-- ts are 3D pixels + mandel value</span></a>
<a class="sourceLine" id="cb46-11" title="11"> ts <span class="ot">=</span> <span class="fu">map</span> (\(u,v,w) <span class="ot">-&gt;</span> (u,v,w,ymandel u v (w<span class="op">+</span><span class="dv">1</span>))) zs</a>
<a class="sourceLine" id="cb46-12" title="12"> <span class="co">-- ps are 3D opengl points + color value</span></a>
<a class="sourceLine" id="cb46-13" title="13"> ps <span class="ot">=</span> <span class="fu">map</span> (\(u,v,w,c') <span class="ot">-&gt;</span></a>
<a class="sourceLine" id="cb46-14" title="14"> (u<span class="op">/</span>width,v<span class="op">/</span>height,w<span class="op">/</span>deep,colorFromValue c')) ts</a>
<a class="sourceLine" id="cb46-15" title="15"> <span class="co">-- If the point diverged too fast, don't display it</span></a>
<a class="sourceLine" id="cb46-16" title="16"> <span class="kw">if</span> (<span class="fu">and</span> <span class="op">$</span> <span class="fu">map</span> (\(_,_,_,c) <span class="ot">-&gt;</span> c<span class="op">&gt;=</span><span class="dv">57</span>) ts)</a>
<a class="sourceLine" id="cb46-17" title="17"> <span class="kw">then</span> []</a>
<a class="sourceLine" id="cb46-18" title="18"> <span class="co">-- Draw two triangles</span></a>
<a class="sourceLine" id="cb46-19" title="19"> <span class="kw">else</span> [ps<span class="op">!!</span><span class="dv">0</span>,ps<span class="op">!!</span><span class="dv">1</span>,ps<span class="op">!!</span><span class="dv">2</span>,ps<span class="op">!!</span><span class="dv">0</span>,ps<span class="op">!!</span><span class="dv">2</span>,ps<span class="op">!!</span><span class="dv">3</span>]</a>
<a class="sourceLine" id="cb46-20" title="20"></a>
<a class="sourceLine" id="cb46-21" title="21"><span class="co">-- given f min max nbtest,</span></a>
<a class="sourceLine" id="cb46-22" title="22"><span class="co">-- considering</span></a>
<a class="sourceLine" id="cb46-23" title="23"><span class="co">-- - f is an increasing function</span></a>
<a class="sourceLine" id="cb46-24" title="24"><span class="co">-- - f(min)=0</span></a>
<a class="sourceLine" id="cb46-25" title="25"><span class="co">-- - f(max)≠0</span></a>
<a class="sourceLine" id="cb46-26" title="26"><span class="co">-- then maxZeroIndex f min max nbtest returns x such that</span></a>
<a class="sourceLine" id="cb46-27" title="27"><span class="co">-- f(x - ε)=0 and f(x + ε)≠0</span></a>
<a class="sourceLine" id="cb46-28" title="28"><span class="co">-- where ε=(max-min)/2^(nbtest+1)</span></a>
<a class="sourceLine" id="cb46-29" title="29">maxZeroIndex func minval maxval <span class="dv">0</span> <span class="ot">=</span> (minval<span class="op">+</span>maxval)<span class="op">/</span><span class="dv">2</span></a>
<a class="sourceLine" id="cb46-30" title="30">maxZeroIndex func minval maxval n <span class="ot">=</span></a>
<a class="sourceLine" id="cb46-31" title="31"> <span class="kw">if</span> (func medpoint) <span class="op">/=</span> <span class="dv">0</span></a>
<a class="sourceLine" id="cb46-32" title="32"> <span class="kw">then</span> maxZeroIndex func minval medpoint (n<span class="op">-</span><span class="dv">1</span>)</a>
<a class="sourceLine" id="cb46-33" title="33"> <span class="kw">else</span> maxZeroIndex func medpoint maxval (n<span class="op">-</span><span class="dv">1</span>)</a>
<a class="sourceLine" id="cb46-34" title="34"> <span class="kw">where</span> medpoint <span class="ot">=</span> (minval<span class="op">+</span>maxval)<span class="op">/</span><span class="dv">2</span></a>
<a class="sourceLine" id="cb46-35" title="35"></a>
<a class="sourceLine" id="cb46-36" title="36">colorFromValue n <span class="ot">=</span></a>
<a class="sourceLine" id="cb46-37" title="37"> <span class="kw">let</span></a>
<a class="sourceLine" id="cb46-38" title="38"><span class="ot"> t ::</span> <span class="dt">Int</span> <span class="ot">-&gt;</span> <span class="dt">GLfloat</span></a>
<a class="sourceLine" id="cb46-39" title="39"> t i <span class="ot">=</span> <span class="fl">0.7</span> <span class="op">+</span> <span class="fl">0.3</span><span class="op">*</span><span class="fu">cos</span>( <span class="fu">fromIntegral</span> i <span class="op">/</span> <span class="dv">10</span> )</a>
<a class="sourceLine" id="cb46-40" title="40"> <span class="kw">in</span></a>
<a class="sourceLine" id="cb46-41" title="41"> ((t n),(t (n<span class="op">+</span><span class="dv">5</span>)),(t (n<span class="op">+</span><span class="dv">10</span>)))</a>
<a class="sourceLine" id="cb46-42" title="42"></a>
<a class="sourceLine" id="cb46-43" title="43">ymandel x y z <span class="ot">=</span> mandel (<span class="dv">2</span><span class="op">*</span>x<span class="op">/</span>width) (<span class="dv">2</span><span class="op">*</span>y<span class="op">/</span>height) (<span class="dv">2</span><span class="op">*</span>z<span class="op">/</span>deep) <span class="dv">64</span></a></code></pre></div>
</div>
<p>This code is cleaner but many things doesnt feel right. First, all the user interaction code is outside our main file. I feel it is okay to hide the detail for the rendering. But I would have preferred to control the user actions.</p>
<p>On the other hand, we continue to handle a lot rendering details. For example, we provide ordered vertices.</p>
<p><a href="code/04_Mandelbulb/Mandelbulb.lhs" class="cut">Download the source code of this section → 04_Mandelbulb/<strong>Mandelbulb.lhs</strong> </a></p>
<hr />
<p><a href="code/05_Mandelbulb/Mandelbulb.lhs" class="cut">Download the source code of this section → 05_Mandelbulb/<strong>Mandelbulb.lhs</strong></a></p>
<h2 id="functional-organization">Functional organization?</h2>
<p>Some points:</p>
<ol type="1">
<li><p>OpenGL and GLUT is done in C. In particular the <code>mainLoop</code> function is a direct link to the C library (FFI). This function is clearly far from the functional paradigm. Could we make this better? We will have two choices:</p>
<ul>
<li>create our own <code>mainLoop</code> function to make it more functional.</li>
<li>deal with the imperative nature of the GLUT <code>mainLoop</code> function.</li>
</ul>
As one of the goal of this article is to understand how to deal with existing libraries and particularly the one coming from imperative languages, we will continue to use the <code>mainLoop</code> function.</li>
<li><p>Our main problem come from user interaction. If you ask “the Internet”, about how to deal with user interaction with a functional paradigm, the main answer is to use <em>functional reactive programming</em> (FRP). I wont use FRP in this article. Instead, Ill use a simpler while less effective way to deal with user interaction. But The method Ill use will be as pure and functional as possible.</p></li>
</ol>
<p>Here is how I imagine things should go. First, what the main loop should look like if we could make our own:</p>
<pre class="no-highlight"><code>functionalMainLoop =
Read user inputs and provide a list of actions
Apply all actions to the World
Display one frame
repetere aeternum</code></pre>
<p>Clearly, ideally we should provide only three parameters to this main loop function:</p>
<ul>
<li>an initial World state</li>
<li>a mapping between the user interactions and functions which modify the world</li>
<li>a function taking two parameters: time and world state and render a new world without user interaction.</li>
</ul>
<p>Here is a real working code, Ive hidden most display functions. The YGL, is a kind of framework to display 3D functions. But it can easily be extended to many kind of representation.</p>
<div class="codehighlight">
<div class="sourceCode" id="cb48"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb48-1" title="1"><span class="kw">import</span> <span class="dt">YGL</span> <span class="co">-- Most the OpenGL Boilerplate</span></a>
<a class="sourceLine" id="cb48-2" title="2"><span class="kw">import</span> <span class="dt">Mandel</span> <span class="co">-- The 3D Mandelbrot maths</span></a></code></pre></div>
</div>
<p>We first set the mapping between user input and actions. The type of each couple should be of the form <code>(user input, f)</code> where (in a first time) <code>f:World -&gt; World</code>. It means, the user input will transform the world state.</p>
<div class="codehighlight">
<div class="sourceCode" id="cb49"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb49-1" title="1"><span class="co">-- Centralize all user input interaction</span></a>
<a class="sourceLine" id="cb49-2" title="2"><span class="ot">inputActionMap ::</span> <span class="dt">InputMap</span> <span class="dt">World</span></a>
<a class="sourceLine" id="cb49-3" title="3">inputActionMap <span class="ot">=</span> inputMapFromList [</a>
<a class="sourceLine" id="cb49-4" title="4"> (<span class="dt">Press</span> <span class="ch">'k'</span> , rotate xdir <span class="dv">5</span>)</a>
<a class="sourceLine" id="cb49-5" title="5"> ,(<span class="dt">Press</span> <span class="ch">'i'</span> , rotate xdir (<span class="op">-</span><span class="dv">5</span>))</a>
<a class="sourceLine" id="cb49-6" title="6"> ,(<span class="dt">Press</span> <span class="ch">'j'</span> , rotate ydir <span class="dv">5</span>)</a>
<a class="sourceLine" id="cb49-7" title="7"> ,(<span class="dt">Press</span> <span class="ch">'l'</span> , rotate ydir (<span class="op">-</span><span class="dv">5</span>))</a>
<a class="sourceLine" id="cb49-8" title="8"> ,(<span class="dt">Press</span> <span class="ch">'o'</span> , rotate zdir <span class="dv">5</span>)</a>
<a class="sourceLine" id="cb49-9" title="9"> ,(<span class="dt">Press</span> <span class="ch">'u'</span> , rotate zdir (<span class="op">-</span><span class="dv">5</span>))</a>
<a class="sourceLine" id="cb49-10" title="10"> ,(<span class="dt">Press</span> <span class="ch">'f'</span> , translate xdir <span class="fl">0.1</span>)</a>
<a class="sourceLine" id="cb49-11" title="11"> ,(<span class="dt">Press</span> <span class="ch">'s'</span> , translate xdir (<span class="op">-</span><span class="fl">0.1</span>))</a>
<a class="sourceLine" id="cb49-12" title="12"> ,(<span class="dt">Press</span> <span class="ch">'e'</span> , translate ydir <span class="fl">0.1</span>)</a>
<a class="sourceLine" id="cb49-13" title="13"> ,(<span class="dt">Press</span> <span class="ch">'d'</span> , translate ydir (<span class="op">-</span><span class="fl">0.1</span>))</a>
<a class="sourceLine" id="cb49-14" title="14"> ,(<span class="dt">Press</span> <span class="ch">'z'</span> , translate zdir <span class="fl">0.1</span>)</a>
<a class="sourceLine" id="cb49-15" title="15"> ,(<span class="dt">Press</span> <span class="ch">'r'</span> , translate zdir (<span class="op">-</span><span class="fl">0.1</span>))</a>
<a class="sourceLine" id="cb49-16" title="16"> ,(<span class="dt">Press</span> <span class="ch">'+'</span> , zoom <span class="fl">1.1</span>)</a>
<a class="sourceLine" id="cb49-17" title="17"> ,(<span class="dt">Press</span> <span class="ch">'-'</span> , zoom (<span class="dv">1</span><span class="op">/</span><span class="fl">1.1</span>))</a>
<a class="sourceLine" id="cb49-18" title="18"> ,(<span class="dt">Press</span> <span class="ch">'h'</span> , resize <span class="fl">1.2</span>)</a>
<a class="sourceLine" id="cb49-19" title="19"> ,(<span class="dt">Press</span> <span class="ch">'g'</span> , resize (<span class="dv">1</span><span class="op">/</span><span class="fl">1.2</span>))</a>
<a class="sourceLine" id="cb49-20" title="20"> ]</a></code></pre></div>
</div>
<p>And of course a type design the World State. The important part is that it is our World State type. We could have used any kind of data type.</p>
<div class="codehighlight">
<div class="sourceCode" id="cb50"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb50-1" title="1"><span class="co">-- I prefer to set my own name for these types</span></a>
<a class="sourceLine" id="cb50-2" title="2"><span class="kw">data</span> <span class="dt">World</span> <span class="ot">=</span> <span class="dt">World</span> {</a>
<a class="sourceLine" id="cb50-3" title="3"><span class="ot"> angle ::</span> <span class="dt">Point3D</span></a>
<a class="sourceLine" id="cb50-4" title="4"> ,<span class="ot"> scale ::</span> <span class="dt">Scalar</span></a>
<a class="sourceLine" id="cb50-5" title="5"> ,<span class="ot"> position ::</span> <span class="dt">Point3D</span></a>
<a class="sourceLine" id="cb50-6" title="6"> ,<span class="ot"> shape ::</span> <span class="dt">Scalar</span> <span class="ot">-&gt;</span> <span class="dt">Function3D</span></a>
<a class="sourceLine" id="cb50-7" title="7"> ,<span class="ot"> box ::</span> <span class="dt">Box3D</span></a>
<a class="sourceLine" id="cb50-8" title="8"> ,<span class="ot"> told ::</span> <span class="dt">Time</span> <span class="co">-- last frame time</span></a>
<a class="sourceLine" id="cb50-9" title="9"> }</a></code></pre></div>
</div>
<p>The important part to glue our own type to the framework is to make our type an instance of the type class <code>DisplayableWorld</code>. We simply have to provide the definition of some functions.</p>
<div class="codehighlight">
<div class="sourceCode" id="cb51"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb51-1" title="1"><span class="kw">instance</span> <span class="dt">DisplayableWorld</span> <span class="dt">World</span> <span class="kw">where</span></a>
<a class="sourceLine" id="cb51-2" title="2"> winTitle _ <span class="ot">=</span> <span class="st">&quot;The YGL Mandelbulb&quot;</span></a>
<a class="sourceLine" id="cb51-3" title="3"> camera w <span class="ot">=</span> <span class="dt">Camera</span> {</a>
<a class="sourceLine" id="cb51-4" title="4"> camPos <span class="ot">=</span> position w,</a>
<a class="sourceLine" id="cb51-5" title="5"> camDir <span class="ot">=</span> angle w,</a>
<a class="sourceLine" id="cb51-6" title="6"> camZoom <span class="ot">=</span> scale w }</a>
<a class="sourceLine" id="cb51-7" title="7"> <span class="co">-- objects for world w</span></a>
<a class="sourceLine" id="cb51-8" title="8"> <span class="co">-- is the list of one unique element</span></a>
<a class="sourceLine" id="cb51-9" title="9"> <span class="co">-- The element is an YObject</span></a>
<a class="sourceLine" id="cb51-10" title="10"> <span class="co">-- more precisely the XYFunc Function3D Box3D</span></a>
<a class="sourceLine" id="cb51-11" title="11"> <span class="co">-- where the Function3D is the type</span></a>
<a class="sourceLine" id="cb51-12" title="12"> <span class="co">-- Point -&gt; Point -&gt; Maybe (Point,Color)</span></a>
<a class="sourceLine" id="cb51-13" title="13"> <span class="co">-- and its value here is ((shape w) res)</span></a>
<a class="sourceLine" id="cb51-14" title="14"> <span class="co">-- and the Box3D value is defbox</span></a>
<a class="sourceLine" id="cb51-15" title="15"> objects w <span class="ot">=</span> [<span class="dt">XYFunc</span> ((shape w) res) defbox]</a>
<a class="sourceLine" id="cb51-16" title="16"> <span class="kw">where</span></a>
<a class="sourceLine" id="cb51-17" title="17"> res <span class="ot">=</span> resolution <span class="op">$</span> box w</a>
<a class="sourceLine" id="cb51-18" title="18"> defbox <span class="ot">=</span> box w</a></code></pre></div>
</div>
<p>The <code>camera</code> function will retrieve an object of type <code>Camera</code> which contains most necessary information to set our camera. The <code>objects</code> function will returns a list of objects. Their type is <code>YObject</code>. Note the generation of triangles is no more in this file. Until here we only used declarative pattern.</p>
<p>We also need to set all our transformation functions. These function are used to update the world state.</p>
<div class="codehighlight">
<div class="sourceCode" id="cb52"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb52-1" title="1"><span class="ot">xdir ::</span> <span class="dt">Point3D</span></a>
<a class="sourceLine" id="cb52-2" title="2">xdir <span class="ot">=</span> makePoint3D (<span class="dv">1</span>,<span class="dv">0</span>,<span class="dv">0</span>)</a>
<a class="sourceLine" id="cb52-3" title="3"><span class="ot">ydir ::</span> <span class="dt">Point3D</span></a>
<a class="sourceLine" id="cb52-4" title="4">ydir <span class="ot">=</span> makePoint3D (<span class="dv">0</span>,<span class="dv">1</span>,<span class="dv">0</span>)</a>
<a class="sourceLine" id="cb52-5" title="5"><span class="ot">zdir ::</span> <span class="dt">Point3D</span></a>
<a class="sourceLine" id="cb52-6" title="6">zdir <span class="ot">=</span> makePoint3D (<span class="dv">0</span>,<span class="dv">0</span>,<span class="dv">1</span>)</a></code></pre></div>
</div>
<p>Note <code>(-*&lt;)</code> is the scalar product (<code>α -*&lt; (x,y,z) = (αx,αy,αz)</code>). Also note we could add two Point3D.</p>
<div class="codehighlight">
<div class="sourceCode" id="cb53"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb53-1" title="1"><span class="ot">rotate ::</span> <span class="dt">Point3D</span> <span class="ot">-&gt;</span> <span class="dt">Scalar</span> <span class="ot">-&gt;</span> <span class="dt">World</span> <span class="ot">-&gt;</span> <span class="dt">World</span></a>
<a class="sourceLine" id="cb53-2" title="2">rotate dir angleValue world <span class="ot">=</span></a>
<a class="sourceLine" id="cb53-3" title="3"> world {</a>
<a class="sourceLine" id="cb53-4" title="4"> angle <span class="ot">=</span> (angle world) <span class="op">+</span> (angleValue <span class="op">-*&lt;</span> dir) }</a>
<a class="sourceLine" id="cb53-5" title="5"></a>
<a class="sourceLine" id="cb53-6" title="6"><span class="ot">translate ::</span> <span class="dt">Point3D</span> <span class="ot">-&gt;</span> <span class="dt">Scalar</span> <span class="ot">-&gt;</span> <span class="dt">World</span> <span class="ot">-&gt;</span> <span class="dt">World</span></a>
<a class="sourceLine" id="cb53-7" title="7">translate dir len world <span class="ot">=</span></a>
<a class="sourceLine" id="cb53-8" title="8"> world {</a>
<a class="sourceLine" id="cb53-9" title="9"> position <span class="ot">=</span> (position world) <span class="op">+</span> (len <span class="op">-*&lt;</span> dir) }</a>
<a class="sourceLine" id="cb53-10" title="10"></a>
<a class="sourceLine" id="cb53-11" title="11"><span class="ot">zoom ::</span> <span class="dt">Scalar</span> <span class="ot">-&gt;</span> <span class="dt">World</span> <span class="ot">-&gt;</span> <span class="dt">World</span></a>
<a class="sourceLine" id="cb53-12" title="12">zoom z world <span class="ot">=</span> world {</a>
<a class="sourceLine" id="cb53-13" title="13"> scale <span class="ot">=</span> z <span class="op">*</span> scale world }</a>
<a class="sourceLine" id="cb53-14" title="14"></a>
<a class="sourceLine" id="cb53-15" title="15"><span class="ot">resize ::</span> <span class="dt">Scalar</span> <span class="ot">-&gt;</span> <span class="dt">World</span> <span class="ot">-&gt;</span> <span class="dt">World</span></a>
<a class="sourceLine" id="cb53-16" title="16">resize r world <span class="ot">=</span> world {</a>
<a class="sourceLine" id="cb53-17" title="17"> box <span class="ot">=</span> (box world) {</a>
<a class="sourceLine" id="cb53-18" title="18"> resolution <span class="ot">=</span> <span class="fu">sqrt</span> ((resolution (box world))<span class="op">**</span><span class="dv">2</span> <span class="op">*</span> r) }}</a></code></pre></div>
</div>
<p>The resize is used to generate the 3D function. As I wanted the time spent to generate a more detailed view to grow linearly I use this not so straightforward formula.</p>
<p>The <code>yMainLoop</code> takes three arguments.</p>
<ul>
<li>A map between user Input and world transformation</li>
<li>A timed world transformation</li>
<li>An initial world state</li>
</ul>
<div class="codehighlight">
<div class="sourceCode" id="cb54"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb54-1" title="1"><span class="ot">main ::</span> <span class="dt">IO</span> ()</a>
<a class="sourceLine" id="cb54-2" title="2">main <span class="ot">=</span> yMainLoop inputActionMap idleAction initialWorld</a></code></pre></div>
</div>
<p>Here is our initial world state.</p>
<div class="codehighlight">
<div class="sourceCode" id="cb55"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb55-1" title="1"><span class="co">-- We initialize the world state</span></a>
<a class="sourceLine" id="cb55-2" title="2"><span class="co">-- then angle, position and zoom of the camera</span></a>
<a class="sourceLine" id="cb55-3" title="3"><span class="co">-- And the shape function</span></a>
<a class="sourceLine" id="cb55-4" title="4"><span class="ot">initialWorld ::</span> <span class="dt">World</span></a>
<a class="sourceLine" id="cb55-5" title="5">initialWorld <span class="ot">=</span> <span class="dt">World</span> {</a>
<a class="sourceLine" id="cb55-6" title="6"> angle <span class="ot">=</span> makePoint3D (<span class="op">-</span><span class="dv">30</span>,<span class="op">-</span><span class="dv">30</span>,<span class="dv">0</span>)</a>
<a class="sourceLine" id="cb55-7" title="7"> , position <span class="ot">=</span> makePoint3D (<span class="dv">0</span>,<span class="dv">0</span>,<span class="dv">0</span>)</a>
<a class="sourceLine" id="cb55-8" title="8"> , scale <span class="ot">=</span> <span class="fl">0.8</span></a>
<a class="sourceLine" id="cb55-9" title="9"> , shape <span class="ot">=</span> shapeFunc</a>
<a class="sourceLine" id="cb55-10" title="10"> , box <span class="ot">=</span> <span class="dt">Box3D</span> { minPoint <span class="ot">=</span> makePoint3D (<span class="op">-</span><span class="dv">2</span>,<span class="op">-</span><span class="dv">2</span>,<span class="op">-</span><span class="dv">2</span>)</a>
<a class="sourceLine" id="cb55-11" title="11"> , maxPoint <span class="ot">=</span> makePoint3D (<span class="dv">2</span>,<span class="dv">2</span>,<span class="dv">2</span>)</a>
<a class="sourceLine" id="cb55-12" title="12"> , resolution <span class="ot">=</span> <span class="fl">0.16</span> }</a>
<a class="sourceLine" id="cb55-13" title="13"> , told <span class="ot">=</span> <span class="dv">0</span></a>
<a class="sourceLine" id="cb55-14" title="14"> }</a></code></pre></div>
</div>
<p>We will define <code>shapeFunc</code> later. Here is the function which transform the world even without user action. Mainly it makes some rotation.</p>
<div class="codehighlight">
<div class="sourceCode" id="cb56"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb56-1" title="1"><span class="ot">idleAction ::</span> <span class="dt">Time</span> <span class="ot">-&gt;</span> <span class="dt">World</span> <span class="ot">-&gt;</span> <span class="dt">World</span></a>
<a class="sourceLine" id="cb56-2" title="2">idleAction tnew world <span class="ot">=</span> world {</a>
<a class="sourceLine" id="cb56-3" title="3"> angle <span class="ot">=</span> (angle world) <span class="op">+</span> (delta <span class="op">-*&lt;</span> zdir)</a>
<a class="sourceLine" id="cb56-4" title="4"> , told <span class="ot">=</span> tnew</a>
<a class="sourceLine" id="cb56-5" title="5"> }</a>
<a class="sourceLine" id="cb56-6" title="6"> <span class="kw">where</span></a>
<a class="sourceLine" id="cb56-7" title="7"> anglePerSec <span class="ot">=</span> <span class="fl">5.0</span></a>
<a class="sourceLine" id="cb56-8" title="8"> delta <span class="ot">=</span> anglePerSec <span class="op">*</span> elapsed <span class="op">/</span> <span class="fl">1000.0</span></a>
<a class="sourceLine" id="cb56-9" title="9"> elapsed <span class="ot">=</span> <span class="fu">fromIntegral</span> (tnew <span class="op">-</span> (told world))</a></code></pre></div>
</div>
<p>Now the function which will generate points in 3D. The first parameter (<code>res</code>) is the resolution of the vertex generation. More precisely, <code>res</code> is distance between two points on one direction. We need it to “close” our shape.</p>
<p>The type <code>Function3D</code> is <code>Point -&gt; Point -&gt; Maybe Point</code>. Because we consider partial functions (for some <code>(x,y)</code> our function can be undefined).</p>
<div class="codehighlight">
<div class="sourceCode" id="cb57"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb57-1" title="1"><span class="ot">shapeFunc ::</span> <span class="dt">Scalar</span> <span class="ot">-&gt;</span> <span class="dt">Function3D</span></a>
<a class="sourceLine" id="cb57-2" title="2">shapeFunc res x y <span class="ot">=</span></a>
<a class="sourceLine" id="cb57-3" title="3"> <span class="kw">let</span></a>
<a class="sourceLine" id="cb57-4" title="4"> z <span class="ot">=</span> maxZeroIndex (ymandel x y) <span class="dv">0</span> <span class="dv">1</span> <span class="dv">20</span></a>
<a class="sourceLine" id="cb57-5" title="5"> <span class="kw">in</span></a>
<a class="sourceLine" id="cb57-6" title="6"> <span class="kw">if</span> <span class="fu">and</span> [ maxZeroIndex (ymandel (x<span class="op">+</span>xeps) (y<span class="op">+</span>yeps)) <span class="dv">0</span> <span class="dv">1</span> <span class="dv">20</span> <span class="op">&lt;</span> <span class="fl">0.000001</span> <span class="op">|</span></a>
<a class="sourceLine" id="cb57-7" title="7"> val <span class="ot">&lt;-</span> [res], xeps <span class="ot">&lt;-</span> [<span class="op">-</span>val,val], yeps<span class="ot">&lt;-</span>[<span class="op">-</span>val,val]]</a>
<a class="sourceLine" id="cb57-8" title="8"> <span class="kw">then</span> <span class="dt">Nothing</span></a>
<a class="sourceLine" id="cb57-9" title="9"> <span class="kw">else</span> <span class="dt">Just</span> (z,colorFromValue ((ymandel x y z) <span class="op">*</span> <span class="dv">64</span>))</a></code></pre></div>
</div>
<p>With the color function.</p>
<div class="codehighlight">
<div class="sourceCode" id="cb58"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb58-1" title="1"><span class="ot">colorFromValue ::</span> <span class="dt">Point</span> <span class="ot">-&gt;</span> <span class="dt">Color</span></a>
<a class="sourceLine" id="cb58-2" title="2">colorFromValue n <span class="ot">=</span></a>
<a class="sourceLine" id="cb58-3" title="3"> <span class="kw">let</span></a>
<a class="sourceLine" id="cb58-4" title="4"><span class="ot"> t ::</span> <span class="dt">Point</span> <span class="ot">-&gt;</span> <span class="dt">Scalar</span></a>
<a class="sourceLine" id="cb58-5" title="5"> t i <span class="ot">=</span> <span class="fl">0.7</span> <span class="op">+</span> <span class="fl">0.3</span><span class="op">*</span><span class="fu">cos</span>( i <span class="op">/</span> <span class="dv">10</span> )</a>
<a class="sourceLine" id="cb58-6" title="6"> <span class="kw">in</span></a>
<a class="sourceLine" id="cb58-7" title="7"> makeColor (t n) (t (n<span class="op">+</span><span class="dv">5</span>)) (t (n<span class="op">+</span><span class="dv">10</span>))</a></code></pre></div>
</div>
<p>The rest is similar to the preceding sections.</p>
<div class="codehighlight">
<div class="sourceCode" id="cb59"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb59-1" title="1"><span class="co">-- given f min max nbtest,</span></a>
<a class="sourceLine" id="cb59-2" title="2"><span class="co">-- considering</span></a>
<a class="sourceLine" id="cb59-3" title="3"><span class="co">-- - f is an increasing function</span></a>
<a class="sourceLine" id="cb59-4" title="4"><span class="co">-- - f(min)=0</span></a>
<a class="sourceLine" id="cb59-5" title="5"><span class="co">-- - f(max)≠0</span></a>
<a class="sourceLine" id="cb59-6" title="6"><span class="co">-- then maxZeroIndex f min max nbtest returns x such that</span></a>
<a class="sourceLine" id="cb59-7" title="7"><span class="co">-- f(x - ε)=0 and f(x + ε)≠0</span></a>
<a class="sourceLine" id="cb59-8" title="8"><span class="co">-- where ε=(max-min)/2^(nbtest+1)</span></a>
<a class="sourceLine" id="cb59-9" title="9"><span class="ot">maxZeroIndex ::</span> (<span class="dt">Fractional</span> a,<span class="dt">Num</span> a,<span class="dt">Num</span> b,<span class="dt">Eq</span> b) <span class="ot">=&gt;</span></a>
<a class="sourceLine" id="cb59-10" title="10"> (a <span class="ot">-&gt;</span> b) <span class="ot">-&gt;</span> a <span class="ot">-&gt;</span> a <span class="ot">-&gt;</span> <span class="dt">Int</span> <span class="ot">-&gt;</span> a</a>
<a class="sourceLine" id="cb59-11" title="11">maxZeroIndex _ minval maxval <span class="dv">0</span> <span class="ot">=</span> (minval<span class="op">+</span>maxval)<span class="op">/</span><span class="dv">2</span></a>
<a class="sourceLine" id="cb59-12" title="12">maxZeroIndex func minval maxval n <span class="ot">=</span></a>
<a class="sourceLine" id="cb59-13" title="13"> <span class="kw">if</span> (func medpoint) <span class="op">/=</span> <span class="dv">0</span></a>
<a class="sourceLine" id="cb59-14" title="14"> <span class="kw">then</span> maxZeroIndex func minval medpoint (n<span class="op">-</span><span class="dv">1</span>)</a>
<a class="sourceLine" id="cb59-15" title="15"> <span class="kw">else</span> maxZeroIndex func medpoint maxval (n<span class="op">-</span><span class="dv">1</span>)</a>
<a class="sourceLine" id="cb59-16" title="16"> <span class="kw">where</span> medpoint <span class="ot">=</span> (minval<span class="op">+</span>maxval)<span class="op">/</span><span class="dv">2</span></a>
<a class="sourceLine" id="cb59-17" title="17"></a>
<a class="sourceLine" id="cb59-18" title="18"><span class="ot">ymandel ::</span> <span class="dt">Point</span> <span class="ot">-&gt;</span> <span class="dt">Point</span> <span class="ot">-&gt;</span> <span class="dt">Point</span> <span class="ot">-&gt;</span> <span class="dt">Point</span></a>
<a class="sourceLine" id="cb59-19" title="19">ymandel x y z <span class="ot">=</span> <span class="fu">fromIntegral</span> (mandel x y z <span class="dv">64</span>) <span class="op">/</span> <span class="dv">64</span></a></code></pre></div>
</div>
<p>I wont explain how the magic occurs here. If you are interested, just read the file <a href="code/05_Mandelbulb/YGL.hs"><code>YGL.hs</code></a>. It is commented a lot.</p>
<ul>
<li><a href="code/05_Mandelbulb/YGL.hs"><code>YGL.hs</code></a>, the 3D rendering framework</li>
<li><a href="code/05_Mandelbulb/Mandel.hs"><code>Mandel</code></a>, the mandel function</li>
<li><a href="code/05_Mandelbulb/ExtComplex.hs"><code>ExtComplex</code></a>, the extended complexes</li>
</ul>
<p><a href="code/05_Mandelbulb/Mandelbulb.lhs" class="cut">Download the source code of this section → 05_Mandelbulb/<strong>Mandelbulb.lhs</strong> </a></p>
<hr />
<p><a href="code/06_Mandelbulb/Mandelbulb.lhs" class="cut">Download the source code of this section → 06_Mandelbulb/<strong>Mandelbulb.lhs</strong></a></p>
<h2 id="optimization">Optimization</h2>
<p>Our code architecture feel very clean. All the meaningful code is in our main file and all display details are externalized. If you read the code of <code>YGL.hs</code>, youll see I didnt made everything perfect. For example, I didnt finished the code of the lights. But I believe it is a good first step and it will be easy to go further. Unfortunately the program of the preceding session is extremely slow. We compute the Mandelbulb for each frame now.</p>
<p>Before our program structure was:</p>
<pre class="no-highlight"><code>Constant Function -&gt; Constant List of Triangles -&gt; Display</code></pre>
<p>Now we have</p>
<pre class="no-highlight"><code>Main loop -&gt; World -&gt; Function -&gt; List of Objects -&gt; Atoms -&gt; Display</code></pre>
<p>The World state could change. The compiler can no more optimize the computation for us. We have to manually explain when to redraw the shape.</p>
<p>To optimize we must do some things in a lower level. Mostly the program remains the same, but it will provide the list of atoms directly.</p>
<div style="display:none">
<div class="codehighlight">
<div class="sourceCode" id="cb62"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb62-1" title="1"><span class="kw">import</span> <span class="dt">YGL</span> <span class="co">-- Most the OpenGL Boilerplate</span></a>
<a class="sourceLine" id="cb62-2" title="2"><span class="kw">import</span> <span class="dt">Mandel</span> <span class="co">-- The 3D Mandelbrot maths</span></a>
<a class="sourceLine" id="cb62-3" title="3"></a>
<a class="sourceLine" id="cb62-4" title="4"><span class="co">-- Centralize all user input interaction</span></a>
<a class="sourceLine" id="cb62-5" title="5"><span class="ot">inputActionMap ::</span> <span class="dt">InputMap</span> <span class="dt">World</span></a>
<a class="sourceLine" id="cb62-6" title="6">inputActionMap <span class="ot">=</span> inputMapFromList [</a>
<a class="sourceLine" id="cb62-7" title="7"> (<span class="dt">Press</span> <span class="ch">' '</span> , switchRotation)</a>
<a class="sourceLine" id="cb62-8" title="8"> ,(<span class="dt">Press</span> <span class="ch">'k'</span> , rotate xdir <span class="dv">5</span>)</a>
<a class="sourceLine" id="cb62-9" title="9"> ,(<span class="dt">Press</span> <span class="ch">'i'</span> , rotate xdir (<span class="op">-</span><span class="dv">5</span>))</a>
<a class="sourceLine" id="cb62-10" title="10"> ,(<span class="dt">Press</span> <span class="ch">'j'</span> , rotate ydir <span class="dv">5</span>)</a>
<a class="sourceLine" id="cb62-11" title="11"> ,(<span class="dt">Press</span> <span class="ch">'l'</span> , rotate ydir (<span class="op">-</span><span class="dv">5</span>))</a>
<a class="sourceLine" id="cb62-12" title="12"> ,(<span class="dt">Press</span> <span class="ch">'o'</span> , rotate zdir <span class="dv">5</span>)</a>
<a class="sourceLine" id="cb62-13" title="13"> ,(<span class="dt">Press</span> <span class="ch">'u'</span> , rotate zdir (<span class="op">-</span><span class="dv">5</span>))</a>
<a class="sourceLine" id="cb62-14" title="14"> ,(<span class="dt">Press</span> <span class="ch">'f'</span> , translate xdir <span class="fl">0.1</span>)</a>
<a class="sourceLine" id="cb62-15" title="15"> ,(<span class="dt">Press</span> <span class="ch">'s'</span> , translate xdir (<span class="op">-</span><span class="fl">0.1</span>))</a>
<a class="sourceLine" id="cb62-16" title="16"> ,(<span class="dt">Press</span> <span class="ch">'e'</span> , translate ydir <span class="fl">0.1</span>)</a>
<a class="sourceLine" id="cb62-17" title="17"> ,(<span class="dt">Press</span> <span class="ch">'d'</span> , translate ydir (<span class="op">-</span><span class="fl">0.1</span>))</a>
<a class="sourceLine" id="cb62-18" title="18"> ,(<span class="dt">Press</span> <span class="ch">'z'</span> , translate zdir <span class="fl">0.1</span>)</a>
<a class="sourceLine" id="cb62-19" title="19"> ,(<span class="dt">Press</span> <span class="ch">'r'</span> , translate zdir (<span class="op">-</span><span class="fl">0.1</span>))</a>
<a class="sourceLine" id="cb62-20" title="20"> ,(<span class="dt">Press</span> <span class="ch">'+'</span> , zoom <span class="fl">1.1</span>)</a>
<a class="sourceLine" id="cb62-21" title="21"> ,(<span class="dt">Press</span> <span class="ch">'-'</span> , zoom (<span class="dv">1</span><span class="op">/</span><span class="fl">1.1</span>))</a>
<a class="sourceLine" id="cb62-22" title="22"> ,(<span class="dt">Press</span> <span class="ch">'h'</span> , resize <span class="fl">2.0</span>)</a>
<a class="sourceLine" id="cb62-23" title="23"> ,(<span class="dt">Press</span> <span class="ch">'g'</span> , resize (<span class="dv">1</span><span class="op">/</span><span class="fl">2.0</span>))</a>
<a class="sourceLine" id="cb62-24" title="24"> ]</a></code></pre></div>
</div>
</div>
<div class="codehighlight">
<div class="sourceCode" id="cb63"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb63-1" title="1"><span class="kw">data</span> <span class="dt">World</span> <span class="ot">=</span> <span class="dt">World</span> {</a>
<a class="sourceLine" id="cb63-2" title="2"><span class="ot"> angle ::</span> <span class="dt">Point3D</span></a>
<a class="sourceLine" id="cb63-3" title="3"> ,<span class="ot"> anglePerSec ::</span> <span class="dt">Scalar</span></a>
<a class="sourceLine" id="cb63-4" title="4"> ,<span class="ot"> scale ::</span> <span class="dt">Scalar</span></a>
<a class="sourceLine" id="cb63-5" title="5"> ,<span class="ot"> position ::</span> <span class="dt">Point3D</span></a>
<a class="sourceLine" id="cb63-6" title="6"> ,<span class="ot"> box ::</span> <span class="dt">Box3D</span></a>
<a class="sourceLine" id="cb63-7" title="7"> ,<span class="ot"> told ::</span> <span class="dt">Time</span></a>
<a class="sourceLine" id="cb63-8" title="8"> <span class="co">-- We replace shape by cache</span></a>
<a class="sourceLine" id="cb63-9" title="9"> ,<span class="ot"> cache ::</span> [<span class="dt">YObject</span>]</a>
<a class="sourceLine" id="cb63-10" title="10"> }</a></code></pre></div>
</div>
<div class="codehighlight">
<div class="sourceCode" id="cb64"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb64-1" title="1"><span class="kw">instance</span> <span class="dt">DisplayableWorld</span> <span class="dt">World</span> <span class="kw">where</span></a>
<a class="sourceLine" id="cb64-2" title="2"> winTitle _ <span class="ot">=</span> <span class="st">&quot;The YGL Mandelbulb&quot;</span></a>
<a class="sourceLine" id="cb64-3" title="3"> camera w <span class="ot">=</span> <span class="dt">Camera</span> {</a>
<a class="sourceLine" id="cb64-4" title="4"> camPos <span class="ot">=</span> position w,</a>
<a class="sourceLine" id="cb64-5" title="5"> camDir <span class="ot">=</span> angle w,</a>
<a class="sourceLine" id="cb64-6" title="6"> camZoom <span class="ot">=</span> scale w }</a>
<a class="sourceLine" id="cb64-7" title="7"> <span class="co">-- We update our objects instanciation</span></a>
<a class="sourceLine" id="cb64-8" title="8"> objects <span class="ot">=</span> cache</a></code></pre></div>
</div>
<div style="display:none">
<div class="codehighlight">
<div class="sourceCode" id="cb65"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb65-1" title="1"><span class="ot">xdir ::</span> <span class="dt">Point3D</span></a>
<a class="sourceLine" id="cb65-2" title="2">xdir <span class="ot">=</span> makePoint3D (<span class="dv">1</span>,<span class="dv">0</span>,<span class="dv">0</span>)</a>
<a class="sourceLine" id="cb65-3" title="3"><span class="ot">ydir ::</span> <span class="dt">Point3D</span></a>
<a class="sourceLine" id="cb65-4" title="4">ydir <span class="ot">=</span> makePoint3D (<span class="dv">0</span>,<span class="dv">1</span>,<span class="dv">0</span>)</a>
<a class="sourceLine" id="cb65-5" title="5"><span class="ot">zdir ::</span> <span class="dt">Point3D</span></a>
<a class="sourceLine" id="cb65-6" title="6">zdir <span class="ot">=</span> makePoint3D (<span class="dv">0</span>,<span class="dv">0</span>,<span class="dv">1</span>)</a>
<a class="sourceLine" id="cb65-7" title="7"></a>
<a class="sourceLine" id="cb65-8" title="8"><span class="ot">rotate ::</span> <span class="dt">Point3D</span> <span class="ot">-&gt;</span> <span class="dt">Scalar</span> <span class="ot">-&gt;</span> <span class="dt">World</span> <span class="ot">-&gt;</span> <span class="dt">World</span></a>
<a class="sourceLine" id="cb65-9" title="9">rotate dir angleValue world <span class="ot">=</span></a>
<a class="sourceLine" id="cb65-10" title="10"> world {</a>
<a class="sourceLine" id="cb65-11" title="11"> angle <span class="ot">=</span> angle world <span class="op">+</span> (angleValue <span class="op">-*&lt;</span> dir) }</a>
<a class="sourceLine" id="cb65-12" title="12"></a>
<a class="sourceLine" id="cb65-13" title="13"><span class="ot">switchRotation ::</span> <span class="dt">World</span> <span class="ot">-&gt;</span> <span class="dt">World</span></a>
<a class="sourceLine" id="cb65-14" title="14">switchRotation world <span class="ot">=</span></a>
<a class="sourceLine" id="cb65-15" title="15"> world {</a>
<a class="sourceLine" id="cb65-16" title="16"> anglePerSec <span class="ot">=</span> <span class="kw">if</span> anglePerSec world <span class="op">&gt;</span> <span class="dv">0</span> <span class="kw">then</span> <span class="dv">0</span> <span class="kw">else</span> <span class="fl">5.0</span> }</a>
<a class="sourceLine" id="cb65-17" title="17"></a>
<a class="sourceLine" id="cb65-18" title="18"><span class="ot">translate ::</span> <span class="dt">Point3D</span> <span class="ot">-&gt;</span> <span class="dt">Scalar</span> <span class="ot">-&gt;</span> <span class="dt">World</span> <span class="ot">-&gt;</span> <span class="dt">World</span></a>
<a class="sourceLine" id="cb65-19" title="19">translate dir len world <span class="ot">=</span></a>
<a class="sourceLine" id="cb65-20" title="20"> world {</a>
<a class="sourceLine" id="cb65-21" title="21"> position <span class="ot">=</span> position world <span class="op">+</span> (len <span class="op">-*&lt;</span> dir) }</a>
<a class="sourceLine" id="cb65-22" title="22"></a>
<a class="sourceLine" id="cb65-23" title="23"><span class="ot">zoom ::</span> <span class="dt">Scalar</span> <span class="ot">-&gt;</span> <span class="dt">World</span> <span class="ot">-&gt;</span> <span class="dt">World</span></a>
<a class="sourceLine" id="cb65-24" title="24">zoom z world <span class="ot">=</span> world {</a>
<a class="sourceLine" id="cb65-25" title="25"> scale <span class="ot">=</span> z <span class="op">*</span> scale world }</a></code></pre></div>
</div>
<div class="codehighlight">
<div class="sourceCode" id="cb66"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb66-1" title="1"><span class="ot">main ::</span> <span class="dt">IO</span> ()</a>
<a class="sourceLine" id="cb66-2" title="2">main <span class="ot">=</span> yMainLoop inputActionMap idleAction initialWorld</a></code></pre></div>
</div>
</div>
<p>Our initial world state is slightly changed:</p>
<div class="codehighlight">
<div class="sourceCode" id="cb67"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb67-1" title="1"><span class="co">-- We initialize the world state</span></a>
<a class="sourceLine" id="cb67-2" title="2"><span class="co">-- then angle, position and zoom of the camera</span></a>
<a class="sourceLine" id="cb67-3" title="3"><span class="co">-- And the shape function</span></a>
<a class="sourceLine" id="cb67-4" title="4"><span class="ot">initialWorld ::</span> <span class="dt">World</span></a>
<a class="sourceLine" id="cb67-5" title="5">initialWorld <span class="ot">=</span> <span class="dt">World</span> {</a>
<a class="sourceLine" id="cb67-6" title="6"> angle <span class="ot">=</span> makePoint3D (<span class="dv">30</span>,<span class="dv">30</span>,<span class="dv">0</span>)</a>
<a class="sourceLine" id="cb67-7" title="7"> , anglePerSec <span class="ot">=</span> <span class="fl">5.0</span></a>
<a class="sourceLine" id="cb67-8" title="8"> , position <span class="ot">=</span> makePoint3D (<span class="dv">0</span>,<span class="dv">0</span>,<span class="dv">0</span>)</a>
<a class="sourceLine" id="cb67-9" title="9"> , scale <span class="ot">=</span> <span class="fl">1.0</span></a>
<a class="sourceLine" id="cb67-10" title="10"> , box <span class="ot">=</span> <span class="dt">Box3D</span> { minPoint <span class="ot">=</span> makePoint3D (<span class="dv">0</span><span class="op">-</span>eps, <span class="dv">0</span><span class="op">-</span>eps, <span class="dv">0</span><span class="op">-</span>eps)</a>
<a class="sourceLine" id="cb67-11" title="11"> , maxPoint <span class="ot">=</span> makePoint3D (<span class="dv">0</span><span class="op">+</span>eps, <span class="dv">0</span><span class="op">+</span>eps, <span class="dv">0</span><span class="op">+</span>eps)</a>
<a class="sourceLine" id="cb67-12" title="12"> , resolution <span class="ot">=</span> <span class="fl">0.02</span> }</a>
<a class="sourceLine" id="cb67-13" title="13"> , told <span class="ot">=</span> <span class="dv">0</span></a>
<a class="sourceLine" id="cb67-14" title="14"> <span class="co">-- We declare cache directly this time</span></a>
<a class="sourceLine" id="cb67-15" title="15"> , cache <span class="ot">=</span> objectFunctionFromWorld initialWorld</a>
<a class="sourceLine" id="cb67-16" title="16"> }</a>
<a class="sourceLine" id="cb67-17" title="17"> <span class="kw">where</span> eps<span class="ot">=</span><span class="dv">2</span></a></code></pre></div>
</div>
<p>The use of <code>eps</code> is a hint to make a better zoom by computing with the right bounds.</p>
<p>We use the <code>YGL.getObject3DFromShapeFunction</code> function directly. This way instead of providing <code>XYFunc</code>, we provide directly a list of Atoms.</p>
<div class="codehighlight">
<div class="sourceCode" id="cb68"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb68-1" title="1"><span class="ot">objectFunctionFromWorld ::</span> <span class="dt">World</span> <span class="ot">-&gt;</span> [<span class="dt">YObject</span>]</a>
<a class="sourceLine" id="cb68-2" title="2">objectFunctionFromWorld w <span class="ot">=</span> [<span class="dt">Atoms</span> atomList]</a>
<a class="sourceLine" id="cb68-3" title="3"> <span class="kw">where</span> atomListPositive <span class="ot">=</span></a>
<a class="sourceLine" id="cb68-4" title="4"> getObject3DFromShapeFunction</a>
<a class="sourceLine" id="cb68-5" title="5"> (shapeFunc (resolution (box w))) (box w)</a>
<a class="sourceLine" id="cb68-6" title="6"> atomList <span class="ot">=</span> atomListPositive <span class="op">++</span></a>
<a class="sourceLine" id="cb68-7" title="7"> <span class="fu">map</span> negativeTriangle atomListPositive</a>
<a class="sourceLine" id="cb68-8" title="8"> negativeTriangle (<span class="dt">ColoredTriangle</span> (p1,p2,p3,c)) <span class="ot">=</span></a>
<a class="sourceLine" id="cb68-9" title="9"> <span class="dt">ColoredTriangle</span> (negz p1,negz p3,negz p2,c)</a>
<a class="sourceLine" id="cb68-10" title="10"> <span class="kw">where</span> negz (<span class="dt">P</span> (x,y,z)) <span class="ot">=</span> <span class="dt">P</span> (x,y,<span class="op">-</span>z)</a></code></pre></div>
</div>
<p>We know that resize is the only world change that necessitate to recompute the list of atoms (triangles). Then we update our world state accordingly.</p>
<div class="codehighlight">
<div class="sourceCode" id="cb69"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb69-1" title="1"><span class="ot">resize ::</span> <span class="dt">Scalar</span> <span class="ot">-&gt;</span> <span class="dt">World</span> <span class="ot">-&gt;</span> <span class="dt">World</span></a>
<a class="sourceLine" id="cb69-2" title="2">resize r world <span class="ot">=</span></a>
<a class="sourceLine" id="cb69-3" title="3"> tmpWorld { cache <span class="ot">=</span> objectFunctionFromWorld tmpWorld }</a>
<a class="sourceLine" id="cb69-4" title="4"> <span class="kw">where</span></a>
<a class="sourceLine" id="cb69-5" title="5"> tmpWorld <span class="ot">=</span> world { box <span class="ot">=</span> (box world) {</a>
<a class="sourceLine" id="cb69-6" title="6"> resolution <span class="ot">=</span> <span class="fu">sqrt</span> ((resolution (box world))<span class="op">**</span><span class="dv">2</span> <span class="op">*</span> r) }}</a></code></pre></div>
</div>
<p>All the rest is exactly the same.</p>
<div style="display:none">
<div class="codehighlight">
<div class="sourceCode" id="cb70"><pre class="sourceCode haskell"><code class="sourceCode haskell"><a class="sourceLine" id="cb70-1" title="1"><span class="ot">idleAction ::</span> <span class="dt">Time</span> <span class="ot">-&gt;</span> <span class="dt">World</span> <span class="ot">-&gt;</span> <span class="dt">World</span></a>
<a class="sourceLine" id="cb70-2" title="2">idleAction tnew world <span class="ot">=</span></a>
<a class="sourceLine" id="cb70-3" title="3"> world {</a>
<a class="sourceLine" id="cb70-4" title="4"> angle <span class="ot">=</span> angle world <span class="op">+</span> (delta <span class="op">-*&lt;</span> zdir)</a>
<a class="sourceLine" id="cb70-5" title="5"> , told <span class="ot">=</span> tnew</a>
<a class="sourceLine" id="cb70-6" title="6"> }</a>
<a class="sourceLine" id="cb70-7" title="7"> <span class="kw">where</span></a>
<a class="sourceLine" id="cb70-8" title="8"> delta <span class="ot">=</span> anglePerSec world <span class="op">*</span> elapsed <span class="op">/</span> <span class="fl">1000.0</span></a>
<a class="sourceLine" id="cb70-9" title="9"> elapsed <span class="ot">=</span> <span class="fu">fromIntegral</span> (tnew <span class="op">-</span> (told world))</a>
<a class="sourceLine" id="cb70-10" title="10"></a>
<a class="sourceLine" id="cb70-11" title="11"><span class="ot">shapeFunc ::</span> <span class="dt">Scalar</span> <span class="ot">-&gt;</span> <span class="dt">Function3D</span></a>
<a class="sourceLine" id="cb70-12" title="12">shapeFunc res x y <span class="ot">=</span></a>
<a class="sourceLine" id="cb70-13" title="13"> <span class="kw">let</span></a>
<a class="sourceLine" id="cb70-14" title="14"> z <span class="ot">=</span> maxZeroIndex (ymandel x y) <span class="dv">0</span> <span class="dv">1</span> <span class="dv">20</span></a>
<a class="sourceLine" id="cb70-15" title="15"> <span class="kw">in</span></a>
<a class="sourceLine" id="cb70-16" title="16"> <span class="kw">if</span> <span class="fu">and</span> [ maxZeroIndex (ymandel (x<span class="op">+</span>xeps) (y<span class="op">+</span>yeps)) <span class="dv">0</span> <span class="dv">1</span> <span class="dv">20</span> <span class="op">&lt;</span> <span class="fl">0.000001</span> <span class="op">|</span></a>
<a class="sourceLine" id="cb70-17" title="17"> val <span class="ot">&lt;-</span> [res], xeps <span class="ot">&lt;-</span> [<span class="op">-</span>val,val], yeps<span class="ot">&lt;-</span>[<span class="op">-</span>val,val]]</a>
<a class="sourceLine" id="cb70-18" title="18"> <span class="kw">then</span> <span class="dt">Nothing</span></a>
<a class="sourceLine" id="cb70-19" title="19"> <span class="kw">else</span> <span class="dt">Just</span> (z,colorFromValue <span class="dv">0</span>)</a>
<a class="sourceLine" id="cb70-20" title="20"></a>
<a class="sourceLine" id="cb70-21" title="21"><span class="ot">colorFromValue ::</span> <span class="dt">Point</span> <span class="ot">-&gt;</span> <span class="dt">Color</span></a>
<a class="sourceLine" id="cb70-22" title="22">colorFromValue n <span class="ot">=</span></a>
<a class="sourceLine" id="cb70-23" title="23"> <span class="kw">let</span></a>
<a class="sourceLine" id="cb70-24" title="24"><span class="ot"> t ::</span> <span class="dt">Point</span> <span class="ot">-&gt;</span> <span class="dt">Scalar</span></a>
<a class="sourceLine" id="cb70-25" title="25"> t i <span class="ot">=</span> <span class="fl">0.0</span> <span class="op">+</span> <span class="fl">0.5</span><span class="op">*</span><span class="fu">cos</span>( i <span class="op">/</span><span class="dv">10</span> )</a>
<a class="sourceLine" id="cb70-26" title="26"> <span class="kw">in</span></a>
<a class="sourceLine" id="cb70-27" title="27"> makeColor (t n) (t (n<span class="op">+</span><span class="dv">5</span>)) (t (n<span class="op">+</span><span class="dv">10</span>))</a>
<a class="sourceLine" id="cb70-28" title="28"></a>
<a class="sourceLine" id="cb70-29" title="29"><span class="co">-- given f min max nbtest,</span></a>
<a class="sourceLine" id="cb70-30" title="30"><span class="co">-- considering</span></a>
<a class="sourceLine" id="cb70-31" title="31"><span class="co">-- - f is an increasing function</span></a>
<a class="sourceLine" id="cb70-32" title="32"><span class="co">-- - f(min)=0</span></a>
<a class="sourceLine" id="cb70-33" title="33"><span class="co">-- - f(max)≠0</span></a>
<a class="sourceLine" id="cb70-34" title="34"><span class="co">-- then maxZeroIndex f min max nbtest returns x such that</span></a>
<a class="sourceLine" id="cb70-35" title="35"><span class="co">-- f(x - ε)=0 and f(x + ε)≠0</span></a>
<a class="sourceLine" id="cb70-36" title="36"><span class="co">-- where ε=(max-min)/2^(nbtest+1)</span></a>
<a class="sourceLine" id="cb70-37" title="37"><span class="ot">maxZeroIndex ::</span> (<span class="dt">Fractional</span> a,<span class="dt">Num</span> a,<span class="dt">Num</span> b,<span class="dt">Eq</span> b) <span class="ot">=&gt;</span></a>
<a class="sourceLine" id="cb70-38" title="38"> (a <span class="ot">-&gt;</span> b) <span class="ot">-&gt;</span> a <span class="ot">-&gt;</span> a <span class="ot">-&gt;</span> <span class="dt">Int</span> <span class="ot">-&gt;</span> a</a>
<a class="sourceLine" id="cb70-39" title="39">maxZeroIndex _ minval maxval <span class="dv">0</span> <span class="ot">=</span> (minval<span class="op">+</span>maxval)<span class="op">/</span><span class="dv">2</span></a>
<a class="sourceLine" id="cb70-40" title="40">maxZeroIndex func minval maxval n <span class="ot">=</span></a>
<a class="sourceLine" id="cb70-41" title="41"> <span class="kw">if</span> func medpoint <span class="op">/=</span> <span class="dv">0</span></a>
<a class="sourceLine" id="cb70-42" title="42"> <span class="kw">then</span> maxZeroIndex func minval medpoint (n<span class="op">-</span><span class="dv">1</span>)</a>
<a class="sourceLine" id="cb70-43" title="43"> <span class="kw">else</span> maxZeroIndex func medpoint maxval (n<span class="op">-</span><span class="dv">1</span>)</a>
<a class="sourceLine" id="cb70-44" title="44"> <span class="kw">where</span> medpoint <span class="ot">=</span> (minval<span class="op">+</span>maxval)<span class="op">/</span><span class="dv">2</span></a>
<a class="sourceLine" id="cb70-45" title="45"></a>
<a class="sourceLine" id="cb70-46" title="46"><span class="ot">ymandel ::</span> <span class="dt">Point</span> <span class="ot">-&gt;</span> <span class="dt">Point</span> <span class="ot">-&gt;</span> <span class="dt">Point</span> <span class="ot">-&gt;</span> <span class="dt">Point</span></a>
<a class="sourceLine" id="cb70-47" title="47">ymandel x y z <span class="ot">=</span> <span class="fu">fromIntegral</span> (mandel x y z <span class="dv">64</span>) <span class="op">/</span> <span class="dv">64</span></a></code></pre></div>
</div>
</div>
<p>And you can also consider minor changes in the <code>YGL.hs</code> source file.</p>
<ul>
<li><a href="code/06_Mandelbulb/YGL.hs"><code>YGL.hs</code></a>, the 3D rendering framework</li>
<li><a href="code/06_Mandelbulb/Mandel.hs"><code>Mandel</code></a>, the mandel function</li>
<li><a href="code/06_Mandelbulb/ExtComplex.hs"><code>ExtComplex</code></a>, the extended complexes</li>
</ul>
<p><a href="code/06_Mandelbulb/Mandelbulb.lhs" class="cut">Download the source code of this section → 06_Mandelbulb/<strong>Mandelbulb.lhs</strong> </a></p>
<h2 id="conclusion">Conclusion</h2>
<p>As we can use imperative style in a functional language, know you can use functional style in imperative languages. This article exposed a way to organize some code in a functional way. Id like to stress the usage of Haskell made it very simple to achieve this.</p>
<p>Once you are used to pure functional style, it is hard not to see all advantages it offers.</p>
<p>The code in the two last sections is completely pure and functional. Furthermore I dont use <code>GLfloat</code>, <code>Color3</code> or any other OpenGL type. If I want to use another library in the future, I would be able to keep all the pure code and simply update the YGL module.</p>
<p>The <code>YGL</code> module can be seen as a “wrapper” around 3D display and user interaction. It is a clean separator between the imperative paradigm and functional paradigm.</p>
<p>If you want to go further, it shouldnt be hard to add parallelism. This should be easy mainly because most of the visible code is pure. Such an optimization would have been harder by using directly the OpenGL library.</p>
<p>You should also want to make a more precise object. Because, the Mandelbulb is clearly not convex. But a precise rendering might be very long from O(n².log(n)) to O(n³).</p>
<section class="footnotes">
<hr />
<ol>
<li id="fn1"><p>Unfortunately, I couldnt make this program to work on my Mac. More precisely, I couldnt make the <a href="http://openil.sourceforge.net/">DevIL</a> library work on Mac to output the image. Yes I have done a <code>brew install libdevil</code>. But even a minimal program who simply write some <code>jpg</code> didnt worked. I tried both with <code>Haskell</code> and <code>C</code>.<a href="#fnref1" class="footnote-back"></a></p></li>
<li id="fn2"><p>Generally in Haskell you need to declare a lot of import lines. This is something I find annoying. In particular, it should be possible to create a special file, Import.hs which make all the necessary import for you, as you generally need them all. I understand why this is cleaner to force the programmer not to do so, but, each time I do a copy/paste, I feel something is wrong. I believe this concern can be generalized to the lack of namespace in Haskell.<a href="#fnref2" class="footnote-back"></a></p></li>
<li id="fn3"><p>I tried <code>Complex Double</code>, <code>Complex Float</code>, this current data type with <code>Double</code> and the actual version <code>Float</code>. For rendering a 1024x1024 Mandelbrot set it takes <code>Complex Double</code> about 6.8s, for <code>Complex Float</code> about 5.1s, for the actual version with <code>Double</code> and <code>Float</code> it takes about <code>1.6</code> sec. See these sources for testing yourself: <a href="https://gist.github.com/2945043">https://gist.github.com/2945043</a>. If you really want to things to go faster, use <code>data Complex = C {-# UNPACK #-} !Float {-# UNPACK #-} !Float</code>. It takes only one second instead of 1.6s.<a href="#fnref3" class="footnote-back"></a></p></li>
</ol>
</section>
</div>
<div id="afterarticle">
<div id="social">
<a href="/rss.xml" target="_blank" rel="noopener noreferrer nofollow" class="social">RSS</a>
·
<a href="https://twitter.com/home?status=http%3A%2F%2Fyannesposito.com/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/%20via%20@yogsototh" target="_blank" rel="noopener noreferrer nofollow" class="social">Tweet</a>
·
<a href="http://www.facebook.com/sharer/sharer.php?u=http%3A%2F%2Fyannesposito.com/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/" target="_blank" rel="noopener noreferrer nofollow" class="social">FB</a>
<br />
<a class="message" href="../../../../Scratch/en/blog/Social-link-the-right-way/">These social sharing links preserve your privacy</a>
</div>
<div id="navigation">
<a href="../../../../">Home</a>
<span class="sep">¦</span>
<a href="../../../../Scratch/en/blog">Blog</a>
<span class="sep">¦</span>
<a href="../../../../Scratch/en/softwares">Softwares</a>
<span class="sep">¦</span>
<a href="../../../../Scratch/en/about">About</a>
</div>
<div id="totop"><a href="#header">↑ Top ↑</a></div>
<div id="bottom">
<div>
Published on 2012-06-15
</div>
<div>
<a href="https://twitter.com/yogsototh">Follow @yogsototh</a>
</div>
<div>
<a rel="license" href="http://creativecommons.org/licenses/by/3.0/deed.en_US">Yann Esposito©</a>
</div>
<div>
Done with
<a href="http://www.vim.org" target="_blank" rel="noopener noreferrer nofollow"><strike>Vim</strike></a>
<a href="http://spacemacs.org" target="_blank" rel="noopener noreferrer nofollow">spacemacs</a>
<span class="pala">&amp;</span>
<a href="http://nanoc.ws" target="_blank" rel="noopener noreferrer nofollow"><strike>nanoc</strike></a>
<a href="http://jaspervdj.be/hakyll" target="_blank" rel="noopener noreferrer nofollow">Hakyll</a>
</div>
<hr />
<div style="max-width: 100%">
<a href="https://cardanohub.org">
<img src="../../../../Scratch/img/ada-logo.png" class="simple" style="height: 16px;
border-radius: 50%;
vertical-align:middle;
display:inline-block;" />
ADA:
</a>
<code style="display:inline-block;
word-wrap:break-word;
text-align: left;
vertical-align: top;
max-width: 85%;">
DdzFFzCqrhtAvdkmATx5Fm8NPJViDy85ZBw13p4XcNzVzvQg8e3vWLXq23JQWFxPEXK6Kvhaxxe7oJt4VMYHxpA2vtCFiP8fziohN6Yp
</code>
</div>
</div>
</div>
</div>
</div>
</body>
</html>