Relecture
This commit is contained in:
parent
570fea3b04
commit
8e0e2fc56a
8 changed files with 153 additions and 130 deletions
|
@ -1,35 +1,49 @@
|
|||
## Introduction
|
||||
|
||||
TODO: write something nice after reading.
|
||||
I wanted to go further than my
|
||||
[preceding article](/Scratch/en/blog/Haskell-the-Hard-Way/) in which I introduced Haskell.
|
||||
|
||||
The B in Benoît B. Mandelbrot stand fro Benoît B. Mandelbrot
|
||||
Instead of arguing that Haskell is better, because it is functional and "Functional Programming! Yeah!", I'll give an example of what benefit
|
||||
functional programming can provide.
|
||||
This article is more about functional paradigm than functional language.
|
||||
The code organization can be used in most imperative language.
|
||||
As Haskell is designed for functional paradigm, it is easier to talk about functional paradigm using it.
|
||||
In reality, in the firsts sections I use an imperative paradigm.
|
||||
As you can use functional paradigm in imperative language,
|
||||
you can also use imperative paradigm in functional languages.
|
||||
|
||||
[^1]: [Source on reddit](http://www.reddit.com/r/math/comments/uirv8/are_math_jokes_okay_here_i_just_came_up_with_one/c4vr2zo)
|
||||
This article is about creating a useful program.
|
||||
It can interact with the user in real time.
|
||||
It uses OpenGL, a library with imperative programming foundations.
|
||||
But the final code will be quite clean.
|
||||
Most of the code will remain in the pure part (no `IO`).
|
||||
|
||||
Steps:
|
||||
I believe the main audience for this article are:
|
||||
|
||||
1. Mandelbrot set with Haskell OpenGL
|
||||
2. Mandelbrot edges
|
||||
3. 3D Mandelbrot because its fun
|
||||
4. Clean the code from full impure and imperative to purer and purer.
|
||||
5. Refactor the code to separate nicely important parts
|
||||
6. Improve efficiency
|
||||
- Haskell programmer looking for an OpengGL tutorial.
|
||||
- People interested in program organization (programming language agnostic).
|
||||
- Fractal lovers and in particular 3D fractal.
|
||||
- Game programmers (any language)
|
||||
|
||||
I wanted to talk about something cool.
|
||||
For example I always wanted to make a Mandelbrot set explorer.
|
||||
I had written a [command line Mandelbrot set generator in Haskell](http://github.com/yogsototh/mandelbrot.git).
|
||||
The cool part of this utility is that it use all the cores to make the computation (it uses the `repa` package)[^001].
|
||||
|
||||
[^001]: Unfortunately, I couldn't make this program to work on my Mac. More precisely, I couldn't make the [DevIL](http://openil.sourceforge.net/) library work on Mac to output the image. Yes I have done a `brew install libdevil`. But even a minimal program who simply write some `jpg` didn't worked.
|
||||
|
||||
This time, 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.
|
||||
|
||||
Here is what you'll end with:
|
||||
|
||||
blogimage("GoldenMandelbulb.png","A golden mandelbulb")
|
||||
|
||||
And here are the intermediate steps:
|
||||
|
||||
blogimage("HGL_Plan.png","The parts of the article")
|
||||
|
||||
From 1 to 3 it will be _dirtier_ and _dirtier_.
|
||||
At 4, we will make some order in this mess!
|
||||
Hopefuly for the best!
|
||||
|
||||
One of the goal of this article is to show some good properties of Haskell.
|
||||
In particular, how to make some real world application with a pure functional language.
|
||||
|
||||
I know drawing a simple mandelbrot set isn't a "real world" application.
|
||||
But the idea is not to show you a real world application which would be hard to follows, but to give you a way to pass from the pure mindset to some real world application.
|
||||
|
||||
To this, I will show you how should progress an application.
|
||||
It is not something easy to show.
|
||||
This is why, I preferred work with a program that generate some image.
|
||||
|
||||
In a real world application, the first constraint would be to work with some framework.
|
||||
And generally an imperative one.
|
||||
Also, the imperative nature of OpenGL make it the perfect choice for an example.
|
||||
|
||||
We start cleaning everything at the 4th part.
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
## First version
|
||||
|
||||
We can consider two parts.
|
||||
The first being mostly some boilerplate[^1].
|
||||
The second part, contain more interesting stuff.
|
||||
Even in this part, there are some necessary boilerplate.
|
||||
But it is due to the OpenGL library this time.
|
||||
The first being mostly some boilerplate[^011].
|
||||
And the second part more focused on OpenGL and content.
|
||||
|
||||
|
||||
[^1]: Generally in Haskell you need to declare a lot of import lines.
|
||||
[^011]: 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.
|
||||
|
@ -50,9 +47,6 @@ We declare some useful functions for manipulating complex numbers:
|
|||
|
||||
### Let us start
|
||||
|
||||
Well, up until here we didn't made something useful.
|
||||
Just a lot of boilerplate and default value.
|
||||
Sorry but it is not completely the end.
|
||||
We start by giving the main architecture of our program:
|
||||
|
||||
> main :: IO ()
|
||||
|
@ -69,7 +63,8 @@ We start by giving the main architecture of our program:
|
|||
> -- We enter the main loop
|
||||
> mainLoop
|
||||
|
||||
The only interesting part is we declared that the function `display` will be used to render the graphics:
|
||||
Mainly, we initialize our OpenGL application.
|
||||
We declared that the function `display` will be used to render the graphics:
|
||||
|
||||
> display = do
|
||||
> clear [ColorBuffer] -- make the window black
|
||||
|
@ -77,12 +72,12 @@ The only interesting part is we declared that the function `display` will be use
|
|||
> preservingMatrix drawMandelbrot
|
||||
> swapBuffers -- refresh screen
|
||||
|
||||
Also here, there is only one interesting part,
|
||||
the draw will occurs in the function `drawMandelbrot`.
|
||||
Also here, there is only one interesting line;
|
||||
the draw will occur in the function `drawMandelbrot`.
|
||||
|
||||
Now we must speak a bit about how OpenGL works.
|
||||
We said that OpenGL is imperative by design.
|
||||
In fact, you must write the list of actions in the right order.
|
||||
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:
|
||||
|
||||
|
@ -111,8 +106,8 @@ drawMandelbrot =
|
|||
~~~
|
||||
|
||||
We also need some kind of global variables.
|
||||
In fact, global variable are a proof of some bad design.
|
||||
But remember it is our first try:
|
||||
In fact, global variable are a proof of a design problem.
|
||||
We will get rid of them later.
|
||||
|
||||
> width = 320 :: GLfloat
|
||||
> height = 320 :: GLfloat
|
||||
|
@ -135,7 +130,7 @@ We need a function which transform an integer value to some color:
|
|||
> in
|
||||
> Color3 (t n) (t (n+5)) (t (n+10))
|
||||
|
||||
And now the mandel function.
|
||||
And now the `mandel` function.
|
||||
Given two coordinates in pixels, it returns some integer value:
|
||||
|
||||
> mandel x y =
|
||||
|
@ -144,8 +139,8 @@ Given two coordinates in pixels, it returns some integer value:
|
|||
> in
|
||||
> f (complex r i) 0 64
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
Let us define \\(f_c: \mathbb{C} \to \mathbb{C}\\)
|
||||
|
||||
|
@ -163,15 +158,15 @@ Of course, instead of trying to test the real limit, we just make a test after a
|
|||
> then n
|
||||
> else f c ((z*z)+c) (n-1)
|
||||
|
||||
Well, if you download this lhs file, compile it and run it this is the result:
|
||||
Well, if you download this file (look at the bottom of this section), compile it and run it this is the result:
|
||||
|
||||
blogimage("hglmandel_v01.png","The mandelbrot set version 1")
|
||||
|
||||
A first very interesting property of this program is that the computation for all the points is done only once.
|
||||
The proof is that it might be a bit long before a first image appears, but if you resize the window, it updates instantaneously.
|
||||
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 `allPoints` is a pure list.
|
||||
Therefore, calling `allPoints` will always render the same result.
|
||||
Therefore, calling `allPoints` will always render the same result and Haskell is clever enough to use this property.
|
||||
While Haskell doesn't garbage collect `allPoints` the result is reused for free.
|
||||
We didn't specified this value should be saved for later use.
|
||||
It is saved for us.
|
||||
|
@ -180,7 +175,6 @@ See what occurs if we make the window bigger:
|
|||
|
||||
blogimage("hglmandel_v01_too_wide.png","The mandelbrot too wide, black lines and columns")
|
||||
|
||||
Yep, we see some black lines.
|
||||
Why? Simply because we drawn less point than there is on the surface.
|
||||
We see some black lines because we 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.
|
||||
|
|
Binary file not shown.
|
@ -51,6 +51,10 @@
|
|||
</div>
|
||||
|
||||
This time, instead of drawing all points, I'll simply want to 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.
|
||||
|
||||
We change slightly the drawMandelbrot function.
|
||||
We replace the `Points` by `LineLoop`
|
||||
|
||||
|
@ -73,18 +77,19 @@ we will choose only point on the surface.
|
|||
> map (\(x,y,c) -> (x,-y,c)) (reverse positivePoints)
|
||||
|
||||
We only need to compute the positive point.
|
||||
The mandelbrot set is symetric on the abscisse axis.
|
||||
The Mandelbrot set is symmetric on the abscisse axis.
|
||||
|
||||
> positivePoints :: [(GLfloat,GLfloat,Color3 GLfloat)]
|
||||
> positivePoints = do
|
||||
> x <- [-width..width]
|
||||
> let y = findMaxOrdFor (mandel x) 0 height 10 -- log height
|
||||
> let y = findMaxOrdFor (mandel x) 0 height (log2 height)
|
||||
> if y < 1 -- We don't draw point in the absciss
|
||||
> then []
|
||||
> else return (x/width,y/height,colorFromValue $ mandel x y)
|
||||
> where
|
||||
> log2 n = floor ((log n) / log 2)
|
||||
|
||||
|
||||
This function is interresting.
|
||||
This function is interesting.
|
||||
For those not used to the list monad here is a natural language version of this function:
|
||||
|
||||
~~~
|
||||
|
@ -96,7 +101,7 @@ positivePoints =
|
|||
~~~
|
||||
|
||||
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 mandel x y > 0 we create a simple dichotomic search:
|
||||
To find the smallest number such that `mandel x y > 0` we use a simple dichotomy:
|
||||
|
||||
> findMaxOrdFor func minval maxval 0 = (minval+maxval)/2
|
||||
> findMaxOrdFor func minval maxval n =
|
||||
|
@ -105,9 +110,7 @@ To find the smallest number such that mandel x y > 0 we create a simple dichotom
|
|||
> else findMaxOrdFor func medpoint maxval (n-1)
|
||||
> where medpoint = (minval+maxval)/2
|
||||
|
||||
No rocket science here.
|
||||
I know, due to the fact the mandelbrot set is not convex this approach does some errors. But the approximation will be good enough.
|
||||
See the result now:
|
||||
No rocket science here. See the result now:
|
||||
|
||||
blogimage("HGLMandelEdges.png","The edges of the mandelbrot set")
|
||||
|
||||
|
|
|
@ -1,20 +1,21 @@
|
|||
## 3D Mandelbrot?
|
||||
|
||||
Why only draw the edge?
|
||||
It is clearly not as nice as drawing the complete surface.
|
||||
Yeah, I know, but, as we use OpenGL, why not show something in 3D.
|
||||
|
||||
But, complex number are only in 2D and there is no 3D equivalent to complex.
|
||||
In fact, the only extension known are quaternions, 4D.
|
||||
As I know almost nothing about quaternions, I will use some extended complex.
|
||||
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 nice.
|
||||
But it will be enough for us to create something that look nice.
|
||||
|
||||
As there is a lot of code, I'll give a high level view to what occurs:
|
||||
This section is quite long, but don't be afraid,
|
||||
most of the code is some OpenGL boilerplate.
|
||||
For those you want to skim,
|
||||
here is a high level representation:
|
||||
|
||||
> - OpenGL Boilerplate
|
||||
>
|
||||
> - set some IORef for states
|
||||
> - set some IORef (understand variables) for states
|
||||
> - Drawing:
|
||||
>
|
||||
> - set doubleBuffer, handle depth, window size...
|
||||
|
@ -49,8 +50,8 @@ As there is a lot of code, I'll give a high level view to what occurs:
|
|||
|
||||
</div>
|
||||
|
||||
We declare a new type `ExtComplex` (for exttended complex).
|
||||
An extension of complex numbers:
|
||||
We declare a new type `ExtComplex` (for extended complex).
|
||||
An extension of complex numbers with a third component:
|
||||
|
||||
> data ExtComplex = C (GLfloat,GLfloat,GLfloat)
|
||||
> deriving (Show,Eq)
|
||||
|
@ -67,7 +68,17 @@ An extension of complex numbers:
|
|||
> signum (C (x,y,z)) = C (signum x, 0, 0)
|
||||
|
||||
The most important part is the new multiplication instance.
|
||||
Modifying this formula will change radically the shape of this somehow 3D mandelbrot.
|
||||
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 _strange_.
|
||||
|
||||
$$ \mathrm{real} ((x,y,z) * (x',y',z')) = xx' - yy' - zz' $$
|
||||
|
||||
$$ \mathrm{im} ((x,y,z) * (x',y',z')) = xy' - yx' + zz' $$
|
||||
|
||||
$$ \mathrm{strange} ((x,y,z) * (x',y',z')) = xz' + zx' $$
|
||||
|
||||
Note how if `z=z'=0` then the multiplication is the same to the complex one.
|
||||
|
||||
<div style="display:none">
|
||||
|
||||
|
@ -104,15 +115,14 @@ And also we will listen the keyboard.
|
|||
> createWindow "3D HOpengGL Mandelbrot"
|
||||
> -- We add some directives
|
||||
> depthFunc $= Just Less
|
||||
> -- matrixMode $= Projection
|
||||
> windowSize $= Size 500 500
|
||||
> -- Some state variables (I know it feels BAD)
|
||||
> angle <- newIORef ((35,0)::(GLfloat,GLfloat))
|
||||
> zoom <- newIORef (2::GLfloat)
|
||||
> campos <- newIORef ((0.7,0)::(GLfloat,GLfloat))
|
||||
> -- Action to call when waiting
|
||||
> -- Function to call each frame
|
||||
> idleCallback $= Just idle
|
||||
> -- We will use the keyboard
|
||||
> -- Function to call when keyboard or mouse is used
|
||||
> keyboardMouseCallback $=
|
||||
> Just (keyboardMouse angle zoom campos)
|
||||
> -- Each time we will need to update the display
|
||||
|
@ -122,12 +132,16 @@ And also we will listen the keyboard.
|
|||
> -- We enter the main loop
|
||||
> mainLoop
|
||||
|
||||
The `idle` function necessary for animation.
|
||||
The `idle` is here to change the states.
|
||||
There should never be any modification done in the `display` function.
|
||||
|
||||
> idle = postRedisplay Nothing
|
||||
|
||||
We introduce some helper function to manipulate
|
||||
standard `IORef`.
|
||||
Mainly `modVar x f` is equivalent to the imperative `x:=f(x)`,
|
||||
`modFst (x,y) (+1)` is equivalent to `(x,y) := (x+1,y)`
|
||||
and `modSnd (x,y) (+1)` is equivalent to `(x,y) := (x,y+1)`
|
||||
|
||||
> modVar v f = do
|
||||
> v' <- get v
|
||||
|
@ -139,23 +153,27 @@ And we use them to code the function handling keyboard.
|
|||
We will use the keys `hjkl` to rotate,
|
||||
`oi` to zoom and `sedf` to move.
|
||||
Also, hitting space will reset the view.
|
||||
Remember that `angle` and `campos` are pairs and `zoom` is a scalar.
|
||||
Also note `(+0.5)` is the function `\x->x+0.5`
|
||||
and `(-0.5)` is the number `-0.5` (yes I share your pain).
|
||||
|
||||
> keyboardMouse angle zoom pos key state modifiers position =
|
||||
> kact angle zoom pos key state
|
||||
> keyboardMouse angle zoom campos key state modifiers position =
|
||||
> -- We won't use modifiers nor position
|
||||
> kact angle zoom campos key state
|
||||
> where
|
||||
> -- reset view when hitting space
|
||||
> kact a z p (Char ' ') Down = do
|
||||
> a $= (0,0)
|
||||
> z $= 1
|
||||
> p $= (0,0)
|
||||
> a $= (0,0) -- angle
|
||||
> z $= 1 -- zoom
|
||||
> p $= (0,0) -- camera position
|
||||
> -- use of hjkl to rotate
|
||||
> kact a _ _ (Char 'h') Down = modVar a (mapFst (+0.5))
|
||||
> kact a _ _ (Char 'l') Down = modVar a (mapFst (+(-0.5)))
|
||||
> kact a _ _ (Char 'j') Down = modVar a (mapSnd (+0.5))
|
||||
> kact a _ _ (Char 'k') Down = modVar a (mapSnd (+(-0.5)))
|
||||
> -- use o and i to zoom
|
||||
> kact _ s _ (Char 'o') Down = modVar s (*1.1)
|
||||
> kact _ s _ (Char 'i') Down = modVar s (*0.9)
|
||||
> kact _ z _ (Char 'o') Down = modVar z (*1.1)
|
||||
> kact _ z _ (Char 'i') Down = modVar z (*0.9)
|
||||
> -- use sdfe to move the camera
|
||||
> kact _ _ p (Char 's') Down = modVar p (mapFst (+0.1))
|
||||
> kact _ _ p (Char 'f') Down = modVar p (mapFst (+(-0.1)))
|
||||
|
@ -164,9 +182,8 @@ Also, hitting space will reset the view.
|
|||
> -- any other keys does nothing
|
||||
> kact _ _ _ _ _ = return ()
|
||||
|
||||
Now, we will show the object using the display function.
|
||||
Note, this time, display take some parameters.
|
||||
Mainly, this function if full of boilerplate:
|
||||
Note `display` take some parameters this time.
|
||||
This function if full of boilerplate:
|
||||
|
||||
> display angle zoom position = do
|
||||
> -- set the background color (dark solarized theme)
|
||||
|
@ -184,9 +201,11 @@ Mainly, this function if full of boilerplate:
|
|||
> (xangle,yangle) <- get angle
|
||||
> rotate xangle $ Vector3 1.0 0.0 (0.0::GLfloat)
|
||||
> rotate yangle $ Vector3 0.0 1.0 (0.0::GLfloat)
|
||||
>
|
||||
> -- Now that all transformation were made
|
||||
> -- We create the object(s)
|
||||
> preservingMatrix drawMandelbrot
|
||||
>
|
||||
> swapBuffers -- refresh screen
|
||||
|
||||
Not much to say about this function.
|
||||
|
@ -194,9 +213,9 @@ Mainly there are two parts: apply some transformations, draw the object.
|
|||
|
||||
### The 3D Mandelbrot
|
||||
|
||||
Now, that we talked about the OpenGL part, let's talk about how we
|
||||
We have finished with the OpenGL section, let's talk about how we
|
||||
generate the 3D points and colors.
|
||||
First, we will set the number of detatils to 180 pixels in the three dimensions.
|
||||
First, we will set the number of details to 200 pixels in the three dimensions.
|
||||
|
||||
> nbDetails = 200 :: GLfloat
|
||||
> width = nbDetails
|
||||
|
@ -205,7 +224,9 @@ First, we will set the number of detatils to 180 pixels in the three dimensions.
|
|||
|
||||
This time, instead of just drawing some line or some group of points,
|
||||
we will show triangles.
|
||||
The idea is that we should provide points three by three.
|
||||
The function `allPoints` will provide a multiple of three points.
|
||||
Each three successive point representing the coordinate of each vertex of a triangle.
|
||||
|
||||
|
||||
> drawMandelbrot = do
|
||||
> -- We will print Points (not triangles for example)
|
||||
|
@ -216,14 +237,13 @@ The idea is that we should provide points three by three.
|
|||
> color c
|
||||
> vertex $ Vertex3 x y z
|
||||
|
||||
Now instead of providing only one point at a time, we will provide six ordered points.
|
||||
In fact, we will provide six ordered points.
|
||||
These points will be used to draw two triangles.
|
||||
|
||||
blogimage("triangles.png","Explain triangles")
|
||||
|
||||
Note in 3D the depth of the point is generally different.
|
||||
The next function is a bit long.
|
||||
An approximative English version is:
|
||||
Here is an approximative English version:
|
||||
|
||||
~~~
|
||||
forall x from -width to width
|
||||
|
@ -243,7 +263,8 @@ depthPoints = do
|
|||
x <- [-width..width]
|
||||
y <- [-height..height]
|
||||
let
|
||||
depthOf x' y' = findMaxOrdFor (mandel x' y') 0 deep 7
|
||||
depthOf x' y' = findMaxOrdFor (mandel x' y') 0 deep logdeep
|
||||
logdeep = floor ((log deep) / log 2)
|
||||
z1 = depthOf x y
|
||||
z2 = depthOf (x+1) y
|
||||
z3 = depthOf (x+1) (y+1)
|
||||
|
@ -263,15 +284,16 @@ depthPoints = do
|
|||
|
||||
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 somehow less readable but more generic refactored function:
|
||||
Here is a harder to read but shorter and more generic rewritten function:
|
||||
|
||||
> depthPoints :: [ColoredPoint]
|
||||
> depthPoints = do
|
||||
> x <- [-width..width]
|
||||
> y <- [0..height]
|
||||
> y <- [-height..height]
|
||||
> let
|
||||
> neighbors = [(x,y),(x+1,y),(x+1,y+1),(x,y+1)]
|
||||
> depthOf (u,v) = findMaxOrdFor (mandel u v) 0 deep 7
|
||||
> depthOf (u,v) = findMaxOrdFor (mandel u v) 0 deep logdeep
|
||||
> logdeep = floor ((log deep) / log 2)
|
||||
> -- zs are 3D points with found depth
|
||||
> zs = map (\(u,v) -> (u,v,depthOf (u,v))) neighbors
|
||||
> -- ts are 3D pixels + mandel value
|
||||
|
@ -288,22 +310,17 @@ Here is a somehow less readable but more generic refactored function:
|
|||
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.
|
||||
|
||||
Also, we didn't searched for negative values.
|
||||
For simplicity, I mirror these values.
|
||||
I haven't even tested if this modified mandelbrot is symetric relatively to the plan {(x,y,z)|z=0}.
|
||||
This modified Mandelbrot is no more symmetric relatively to the plan `y=0`.
|
||||
But it is symmetric relatively to the plan `z=0`.
|
||||
Then I mirror these values.
|
||||
|
||||
> allPoints :: [ColoredPoint]
|
||||
> allPoints = planPoints ++ map inverseDepth planPoints
|
||||
> where
|
||||
> planPoints = depthPoints ++ map inverseHeight depthPoints
|
||||
> inverseHeight (x,y,z,c) = (x,-y,z,c)
|
||||
> planPoints = depthPoints
|
||||
> inverseDepth (x,y,z,c) = (x,y,-z+1/deep,c)
|
||||
|
||||
I cheat by making these symmetry.
|
||||
But it is faster and render a nice form.
|
||||
For this tutorial it will be good enough.
|
||||
Also, the dichotomic method I use is mostly right but false for some cases.
|
||||
|
||||
The rest of the program is very close to the preceeding one.
|
||||
The rest of the program is very close to the preceding one.
|
||||
|
||||
<div style="display:none">
|
||||
|
||||
|
@ -333,7 +350,8 @@ We only changed from `Complex` to `ExtComplex` of the main `f` function.
|
|||
|
||||
</div>
|
||||
|
||||
We simply add a new dimenstion to the mandel function. Also we simply need to change the type signature of the function `f` from `Complex` to `ExtComplex`.
|
||||
We simply add a new dimension to the `mandel` function
|
||||
and change the type signature of `f` from `Complex` to `ExtComplex`.
|
||||
|
||||
> mandel x y z =
|
||||
> let r = 2.0 * x / width
|
||||
|
@ -343,9 +361,6 @@ We simply add a new dimenstion to the mandel function. Also we simply need to ch
|
|||
> f (extcomplex r i s) 0 64
|
||||
|
||||
|
||||
And here is the result (if you use 500 for `nbDetails`):
|
||||
Here is the result:
|
||||
|
||||
blogimage("mandelbrot_3D.png","A 3D mandelbrot like")
|
||||
|
||||
This image is quite nice.
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
## Cleaning the code
|
||||
## Naïve code cleaning
|
||||
|
||||
The first thing to do is to separate the GLUT/OpenGL
|
||||
part from the computation of the shape.
|
||||
Here is the cleaned version of the preceeding section.
|
||||
Here is the cleaned version of the preceding section.
|
||||
Most boilerplate was put in external files.
|
||||
|
||||
- [`YBoiler.hs`](code/04_Mandelbulb/YBoiler.hs), the 3D rendering
|
||||
|
@ -79,6 +79,3 @@ But I would have preferred to control the user actions.
|
|||
|
||||
On the other hand, we continue to handle a lot rendering details.
|
||||
For example, we provide ordered vertices.
|
||||
I feel, this should be externalized.
|
||||
|
||||
I would have preferred to make things a bit more general.
|
||||
|
|
|
@ -15,11 +15,11 @@ tags:
|
|||
- functional
|
||||
- tutorial
|
||||
-----
|
||||
blogimage("HGL_Plan.png","The plan in image")
|
||||
blogimage("BenoitBMandelbrot.jpg","The B in Benoît B. Mandelbrot stand for Benoît B. Mandelbrot")
|
||||
|
||||
begindiv(intro)
|
||||
|
||||
en: %tldr A progressive real world example.
|
||||
fr: %tlal Un exemple progressif de programmation avec Haskell.
|
||||
en: %tldr You will see how to go from theory to a real application using Haskell.
|
||||
fr: %tlal Un exemple progressif d'utilisation d'Haskell.
|
||||
|
||||
|
||||
|
|
|
@ -25,8 +25,8 @@ END
|
|||
for fic in **/*.lhs(.N); do
|
||||
contains_haskell=$(( $( egrep '^>' $fic | wc -l) > 0 ))
|
||||
((contains_haskell)) && \
|
||||
print -- "\n<hr/><a href=\"code/$fic\" class=\"cut\">${fic:h}/<strong>${fic:t}</strong></a>\n"
|
||||
print -- "\n<hr/><a href=\"code/$fic\" class=\"cut\">Download the source code of this section → ${fic:h}/<strong>${fic:t}</strong></a>\n"
|
||||
cat $fic
|
||||
((contains_haskell)) && \
|
||||
print -- "\n<a href=\"code/$fic\" class=\"cut\">${fic:h}/<strong>${fic:t}</strong> </a>\n"
|
||||
print -- "\n<a href=\"code/$fic\" class=\"cut\">Download the source code of this section → ${fic:h}/<strong>${fic:t}</strong> </a>\n"
|
||||
done | perl -pe 'BEGIN{$/="";} s#((^>.*\n)+)#<div class="codehighlight">\n<code class="haskell">\n$1</code>\n</div>\n#mg' | perl -pe 's#^> ?##' | perl -pe 's/^ #/#/'
|
||||
|
|
Loading…
Reference in a new issue