diff --git a/.gitignore b/.gitignore index 4e062400e..c051bbfe6 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,11 @@ tmp/ recupen.pl recupfr.pl .sass-cache + +# output/Scratch/**/index.html +output/Scratch/*/index.html +output/Scratch/*/*/index.html +output/Scratch/*/*/*/index.html +output/Scratch/*/*/*/*/index.html +output/Scratch/*/*/*/*/*/index.html +output/Scratch/*/*/*/*/*/*/index.html diff --git a/content/html/en/blog/Haskell-OpenGL-Mandelbrot.md b/content/html/en/blog/Haskell-OpenGL-Mandelbrot.md index f3e138eb4..4ca44aae5 100644 --- a/content/html/en/blog/Haskell-OpenGL-Mandelbrot.md +++ b/content/html/en/blog/Haskell-OpenGL-Mandelbrot.md @@ -13,11 +13,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) -%tldr A progressive real world example. +%tldr You will see how to go from theory to a real application using Haskell. >

Table of Content
@@ -30,46 +30,63 @@ enddiv ## 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. -Steps: +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. 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 +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`). + +I believe the main audience for this article are: + +- 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! +We start cleaning everything at the 4th part. -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. - -
01_Introduction/hglmandel.lhs +
Download the source code of this section → 01_Introduction/hglmandel.lhs ## 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. @@ -126,9 +143,6 @@ magnitude = real.abs ### 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:
@@ -149,7 +163,8 @@ main = do
-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:
@@ -161,12 +176,12 @@ display = do
-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: @@ -199,8 +214,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.
@@ -235,7 +250,7 @@ colorFromValue n =
-And now the mandel function. +And now the `mandel` function. Given two coordinates in pixels, it returns some integer value:
@@ -248,8 +263,8 @@ mandel x y =
-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}\\) @@ -271,15 +286,15 @@ f c z n = if (magnitude z > 2 ) -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. @@ -288,14 +303,13 @@ 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. -01_Introduction/hglmandel.lhs +Download the source code of this section → 01_Introduction/hglmandel.lhs -
02_Edges/HGLMandelEdge.lhs +
Download the source code of this section → 02_Edges/HGLMandelEdge.lhs ## Only the edges @@ -353,6 +367,10 @@ height = 320 :: GLfloat 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` @@ -383,21 +401,23 @@ allPoints = 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 - if y < 1 -- We don't draw point in the absciss - then [] - else return (x/width,y/height,colorFromValue $ mandel x y) + x <- [-width..width] + 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: ~~~ @@ -409,7 +429,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:
@@ -422,9 +442,7 @@ findMaxOrdFor func minval maxval n =
-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") @@ -463,27 +481,28 @@ f c z n = if (magnitude z > 2 ) -02_Edges/HGLMandelEdge.lhs +Download the source code of this section → 02_Edges/HGLMandelEdge.lhs -
03_Mandelbulb/Mandelbulb.lhs +
Download the source code of this section → 03_Mandelbulb/Mandelbulb.lhs ## 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... @@ -520,8 +539,8 @@ type ColoredPoint = (GLfloat,GLfloat,GLfloat,Color3 GLfloat) -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:
@@ -542,7 +561,17 @@ instance Num ExtComplex where
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.
@@ -585,15 +614,14 @@ main = do 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 @@ -605,7 +633,8 @@ main = do
-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.
@@ -615,6 +644,9 @@ 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)`
@@ -630,25 +662,29 @@ 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))) @@ -659,9 +695,8 @@ keyboardMouse angle zoom pos key state modifiers position =
-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:
@@ -681,9 +716,11 @@ display angle zoom position = do (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
@@ -693,9 +730,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.
@@ -708,7 +745,8 @@ deep = nbDetails 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.
@@ -723,14 +761,13 @@ drawMandelbrot = do
-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 @@ -750,7 +787,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) @@ -759,10 +797,10 @@ depthPoints = do c2 = mandel (x+1) y (z2+1) c3 = mandel (x+1) (y+1) (z3+1) c4 = mandel x (y+1) (z4+1) - p1 = ( x /width, y /height, z1/deep,colorFromValue c1) - p2 = ((x+1)/width, y /height, z2/deep,colorFromValue c2) - p3 = ((x+1)/width,(y+1)/height, z3/deep,colorFromValue c3) - p4 = ( x /width,(y+1)/height, z4/deep,colorFromValue c4) + p1 = ( x /width, y /height, z1/deep, colorFromValue c1) + p2 = ((x+1)/width, y /height, z2/deep, colorFromValue c2) + p3 = ((x+1)/width,(y+1)/height, z3/deep, colorFromValue c3) + p4 = ( x /width,(y+1)/height, z4/deep, colorFromValue c4) if (and $ map (>=57) [c1,c2,c3,c4]) then [] else [p1,p2,p3,p1,p3,p4] @@ -770,17 +808,18 @@ 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 @@ -799,26 +838,21 @@ depthPoints = do 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.
@@ -860,7 +894,8 @@ f c z n = if (magnitude z > 2 )
-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`.
@@ -873,21 +908,19 @@ mandel x y z =
-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. +Download the source code of this section → 03_Mandelbulb/Mandelbulb.lhs -03_Mandelbulb/Mandelbulb.lhs +
Download the source code of this section → 04_Mandelbulb/Mandelbulb.lhs -
04_Mandelbulb/Mandelbulb.lhs - -## 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 @@ -984,13 +1017,10 @@ 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. +Download the source code of this section → 04_Mandelbulb/Mandelbulb.lhs -04_Mandelbulb/Mandelbulb.lhs - -
05_Mandelbulb/Mandelbulb.lhs +
Download the source code of this section → 05_Mandelbulb/Mandelbulb.lhs ## Functional organization? @@ -1012,7 +1042,7 @@ Some points: Then here is how I imagine things should go. First, what the main loop should look like: - + functionalMainLoop = Read user inputs and provide a list of actions Apply all actions to the World @@ -1269,16 +1299,16 @@ This file is commented a lot. - [`Mandel`](code/05_Mandelbulb/Mandel.hs), the mandel function - [`ExtComplex`](code/05_Mandelbulb/ExtComplex.hs), the extended complexes -05_Mandelbulb/Mandelbulb.lhs +Download the source code of this section → 05_Mandelbulb/Mandelbulb.lhs -
06_Mandelbulb/Mandelbulb.lhs +
Download the source code of this section → 06_Mandelbulb/Mandelbulb.lhs ## Optimization All feel good from the architecture point of vue. More precisely, the separation between rendering and world behavior is clear. But this is extremely slow now. -Because we compute the mandelbulb for each frame now. +Because we compute the Mandelbulb for each frame now. Before we had @@ -1308,7 +1338,8 @@ import Mandel -- The 3D Mandelbrot maths -- Centralize all user input interaction inputActionMap :: InputMap World inputActionMap = inputMapFromList [ - (Press 'k' , rotate xdir 5) + (Press ' ' , switch_rotation) + ,(Press 'k' , rotate xdir 5) ,(Press 'i' , rotate xdir (-5)) ,(Press 'j' , rotate ydir 5) ,(Press 'l' , rotate ydir (-5)) @@ -1322,8 +1353,8 @@ inputActionMap = inputMapFromList [ ,(Press 'r' , translate zdir (-0.1)) ,(Press '+' , zoom 1.1) ,(Press '-' , zoom (1/1.1)) - ,(Press 'h' , resize 1.2) - ,(Press 'g' , resize (1/1.2)) + ,(Press 'h' , resize 2.0) + ,(Press 'g' , resize (1/2.0)) ]
@@ -1334,6 +1365,7 @@ inputActionMap = inputMapFromList [ data World = World { angle :: Point3D + , anglePerSec :: Scalar , scale :: Scalar , position :: Point3D , box :: Box3D @@ -1373,6 +1405,11 @@ rotate dir angleValue world = world { angle = (angle world) + (angleValue -*< dir) } +switch_rotation :: World -> World +switch_rotation world = + world { + anglePerSec = if anglePerSec world > 0 then 0 else 5.0 } + translate :: Point3D -> Scalar -> World -> World translate dir len world = world { @@ -1403,11 +1440,12 @@ Our initial world state is slightly changed: initialWorld :: World initialWorld = World { angle = makePoint3D (30,30,0) + , anglePerSec = 5.0 , position = makePoint3D (0,0,0) , scale = 1.0 , box = Box3D { minPoint = makePoint3D (-2,-2,-2) , maxPoint = makePoint3D (2,2,2) - , resolution = 0.02 } + , resolution = 0.03 } , told = 0 -- We declare cache directly this time , cache = objectFunctionFromWorld initialWorld @@ -1423,11 +1461,12 @@ This way instead of providing `XYFunc`, we provide directly a list of Atoms. objectFunctionFromWorld :: World -> [YObject] objectFunctionFromWorld w = [Atoms atomList] where atomListPositive = - getObject3DFromShapeFunction (shapeFunc (resolution (box w))) (box w) + getObject3DFromShapeFunction + (shapeFunc (resolution (box w))) (box w) atomList = atomListPositive ++ map negativeTriangle atomListPositive negativeTriangle (ColoredTriangle (p1,p2,p3,c)) = - ColoredTriangle (negz p1,negz p2,negz p3,c) + ColoredTriangle (negz p1,negz p3,negz p2,c) where negz (P (x,y,z)) = P (x,y,-z)
@@ -1460,10 +1499,18 @@ idleAction tnew world = , told = tnew } where - anglePerSec = 5.0 - delta = anglePerSec * elapsed / 1000.0 + delta = anglePerSec world * elapsed / 1000.0 elapsed = fromIntegral (tnew - (told world)) +shapeFunc' :: Scalar -> Function3D +shapeFunc' res x y = if or [tmp u v>=0 | u<-[x,x+res], v<-[y,y+res]] + then Just (z,hexColor "#AD4") + else Nothing + where tmp x y = (x**2 + y**2) + protectSqrt t = if t<0 then 0 else sqrt t + z = sqrt (a**2 - (c - protectSqrt(tmp x y))**2) + a = 0.2 + c = 0.5 shapeFunc :: Scalar -> Function3D shapeFunc res x y = let @@ -1472,13 +1519,13 @@ shapeFunc res x y = if and [ findMaxOrdFor (ymandel (x+xeps) (y+yeps)) 0 1 20 < 0.000001 | val <- [res], xeps <- [-val,val], yeps<-[-val,val]] then Nothing - else Just (z,colorFromValue ((ymandel x y z) * 64)) + else Just (z,colorFromValue 0) colorFromValue :: Point -> Color colorFromValue n = let t :: Point -> Scalar - t i = 0.7 + 0.3*cos( i / 10 ) + t i = 0.0 + 0.5*cos( i /10 ) in makeColor (t n) (t (n+5)) (t (n+10)) @@ -1502,5 +1549,5 @@ ymandel x y z = fromIntegral (mandel x y z 64) / 64 - [`Mandel`](code/06_Mandelbulb/Mandel.hs), the mandel function - [`ExtComplex`](code/06_Mandelbulb/ExtComplex.hs), the extended complexes -06_Mandelbulb/Mandelbulb.lhs +Download the source code of this section → 06_Mandelbulb/Mandelbulb.lhs diff --git a/content/html/fr/blog/Haskell-OpenGL-Mandelbrot.md b/content/html/fr/blog/Haskell-OpenGL-Mandelbrot.md index 88a07ce2a..6b486be17 100644 --- a/content/html/fr/blog/Haskell-OpenGL-Mandelbrot.md +++ b/content/html/fr/blog/Haskell-OpenGL-Mandelbrot.md @@ -13,11 +13,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) -%tlal Un exemple progressif de programmation avec Haskell. +%tlal Un exemple progressif d'utilisation d'Haskell. >

Table of Content
@@ -30,46 +30,63 @@ enddiv ## 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. -Steps: +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. 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 +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`). + +I believe the main audience for this article are: + +- 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! +We start cleaning everything at the 4th part. -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. - -
01_Introduction/hglmandel.lhs +
Download the source code of this section → 01_Introduction/hglmandel.lhs ## 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. @@ -126,9 +143,6 @@ magnitude = real.abs ### 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:
@@ -149,7 +163,8 @@ main = do
-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:
@@ -161,12 +176,12 @@ display = do
-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: @@ -199,8 +214,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.
@@ -235,7 +250,7 @@ colorFromValue n =
-And now the mandel function. +And now the `mandel` function. Given two coordinates in pixels, it returns some integer value:
@@ -248,8 +263,8 @@ mandel x y =
-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}\\) @@ -271,15 +286,15 @@ f c z n = if (magnitude z > 2 )
-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. @@ -288,14 +303,13 @@ 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. -01_Introduction/hglmandel.lhs +Download the source code of this section → 01_Introduction/hglmandel.lhs -
02_Edges/HGLMandelEdge.lhs +
Download the source code of this section → 02_Edges/HGLMandelEdge.lhs ## Only the edges @@ -353,6 +367,10 @@ height = 320 :: GLfloat
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` @@ -383,21 +401,23 @@ allPoints = 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 - if y < 1 -- We don't draw point in the absciss - then [] - else return (x/width,y/height,colorFromValue $ mandel x y) + x <- [-width..width] + 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: ~~~ @@ -409,7 +429,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:
@@ -422,9 +442,7 @@ findMaxOrdFor func minval maxval n =
-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") @@ -463,27 +481,28 @@ f c z n = if (magnitude z > 2 ) -02_Edges/HGLMandelEdge.lhs +Download the source code of this section → 02_Edges/HGLMandelEdge.lhs -
03_Mandelbulb/Mandelbulb.lhs +
Download the source code of this section → 03_Mandelbulb/Mandelbulb.lhs ## 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... @@ -520,8 +539,8 @@ type ColoredPoint = (GLfloat,GLfloat,GLfloat,Color3 GLfloat) -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:
@@ -542,7 +561,17 @@ instance Num ExtComplex where
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.
@@ -585,15 +614,14 @@ main = do 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 @@ -605,7 +633,8 @@ main = do
-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.
@@ -615,6 +644,9 @@ 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)`
@@ -630,25 +662,29 @@ 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))) @@ -659,9 +695,8 @@ keyboardMouse angle zoom pos key state modifiers position =
-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:
@@ -681,9 +716,11 @@ display angle zoom position = do (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
@@ -693,9 +730,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.
@@ -708,7 +745,8 @@ deep = nbDetails 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.
@@ -723,14 +761,13 @@ drawMandelbrot = do
-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 @@ -750,7 +787,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) @@ -759,10 +797,10 @@ depthPoints = do c2 = mandel (x+1) y (z2+1) c3 = mandel (x+1) (y+1) (z3+1) c4 = mandel x (y+1) (z4+1) - p1 = ( x /width, y /height, z1/deep,colorFromValue c1) - p2 = ((x+1)/width, y /height, z2/deep,colorFromValue c2) - p3 = ((x+1)/width,(y+1)/height, z3/deep,colorFromValue c3) - p4 = ( x /width,(y+1)/height, z4/deep,colorFromValue c4) + p1 = ( x /width, y /height, z1/deep, colorFromValue c1) + p2 = ((x+1)/width, y /height, z2/deep, colorFromValue c2) + p3 = ((x+1)/width,(y+1)/height, z3/deep, colorFromValue c3) + p4 = ( x /width,(y+1)/height, z4/deep, colorFromValue c4) if (and $ map (>=57) [c1,c2,c3,c4]) then [] else [p1,p2,p3,p1,p3,p4] @@ -770,17 +808,18 @@ 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 @@ -799,26 +838,21 @@ depthPoints = do 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.
@@ -860,7 +894,8 @@ f c z n = if (magnitude z > 2 )
-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`.
@@ -873,21 +908,19 @@ mandel x y z =
-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. +Download the source code of this section → 03_Mandelbulb/Mandelbulb.lhs -03_Mandelbulb/Mandelbulb.lhs +
Download the source code of this section → 04_Mandelbulb/Mandelbulb.lhs -
04_Mandelbulb/Mandelbulb.lhs - -## 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 @@ -984,13 +1017,10 @@ 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. +Download the source code of this section → 04_Mandelbulb/Mandelbulb.lhs -04_Mandelbulb/Mandelbulb.lhs - -
05_Mandelbulb/Mandelbulb.lhs +
Download the source code of this section → 05_Mandelbulb/Mandelbulb.lhs ## Functional organization? @@ -1012,7 +1042,7 @@ Some points: Then here is how I imagine things should go. First, what the main loop should look like: - + functionalMainLoop = Read user inputs and provide a list of actions Apply all actions to the World @@ -1269,16 +1299,16 @@ This file is commented a lot. - [`Mandel`](code/05_Mandelbulb/Mandel.hs), the mandel function - [`ExtComplex`](code/05_Mandelbulb/ExtComplex.hs), the extended complexes -05_Mandelbulb/Mandelbulb.lhs +Download the source code of this section → 05_Mandelbulb/Mandelbulb.lhs -
06_Mandelbulb/Mandelbulb.lhs +
Download the source code of this section → 06_Mandelbulb/Mandelbulb.lhs ## Optimization All feel good from the architecture point of vue. More precisely, the separation between rendering and world behavior is clear. But this is extremely slow now. -Because we compute the mandelbulb for each frame now. +Because we compute the Mandelbulb for each frame now. Before we had @@ -1308,7 +1338,8 @@ import Mandel -- The 3D Mandelbrot maths -- Centralize all user input interaction inputActionMap :: InputMap World inputActionMap = inputMapFromList [ - (Press 'k' , rotate xdir 5) + (Press ' ' , switch_rotation) + ,(Press 'k' , rotate xdir 5) ,(Press 'i' , rotate xdir (-5)) ,(Press 'j' , rotate ydir 5) ,(Press 'l' , rotate ydir (-5)) @@ -1322,8 +1353,8 @@ inputActionMap = inputMapFromList [ ,(Press 'r' , translate zdir (-0.1)) ,(Press '+' , zoom 1.1) ,(Press '-' , zoom (1/1.1)) - ,(Press 'h' , resize 1.2) - ,(Press 'g' , resize (1/1.2)) + ,(Press 'h' , resize 2.0) + ,(Press 'g' , resize (1/2.0)) ]
@@ -1334,6 +1365,7 @@ inputActionMap = inputMapFromList [ data World = World { angle :: Point3D + , anglePerSec :: Scalar , scale :: Scalar , position :: Point3D , box :: Box3D @@ -1373,6 +1405,11 @@ rotate dir angleValue world = world { angle = (angle world) + (angleValue -*< dir) } +switch_rotation :: World -> World +switch_rotation world = + world { + anglePerSec = if anglePerSec world > 0 then 0 else 5.0 } + translate :: Point3D -> Scalar -> World -> World translate dir len world = world { @@ -1403,11 +1440,12 @@ Our initial world state is slightly changed: initialWorld :: World initialWorld = World { angle = makePoint3D (30,30,0) + , anglePerSec = 5.0 , position = makePoint3D (0,0,0) , scale = 1.0 , box = Box3D { minPoint = makePoint3D (-2,-2,-2) , maxPoint = makePoint3D (2,2,2) - , resolution = 0.02 } + , resolution = 0.03 } , told = 0 -- We declare cache directly this time , cache = objectFunctionFromWorld initialWorld @@ -1423,11 +1461,12 @@ This way instead of providing `XYFunc`, we provide directly a list of Atoms. objectFunctionFromWorld :: World -> [YObject] objectFunctionFromWorld w = [Atoms atomList] where atomListPositive = - getObject3DFromShapeFunction (shapeFunc (resolution (box w))) (box w) + getObject3DFromShapeFunction + (shapeFunc (resolution (box w))) (box w) atomList = atomListPositive ++ map negativeTriangle atomListPositive negativeTriangle (ColoredTriangle (p1,p2,p3,c)) = - ColoredTriangle (negz p1,negz p2,negz p3,c) + ColoredTriangle (negz p1,negz p3,negz p2,c) where negz (P (x,y,z)) = P (x,y,-z)
@@ -1460,10 +1499,18 @@ idleAction tnew world = , told = tnew } where - anglePerSec = 5.0 - delta = anglePerSec * elapsed / 1000.0 + delta = anglePerSec world * elapsed / 1000.0 elapsed = fromIntegral (tnew - (told world)) +shapeFunc' :: Scalar -> Function3D +shapeFunc' res x y = if or [tmp u v>=0 | u<-[x,x+res], v<-[y,y+res]] + then Just (z,hexColor "#AD4") + else Nothing + where tmp x y = (x**2 + y**2) + protectSqrt t = if t<0 then 0 else sqrt t + z = sqrt (a**2 - (c - protectSqrt(tmp x y))**2) + a = 0.2 + c = 0.5 shapeFunc :: Scalar -> Function3D shapeFunc res x y = let @@ -1472,13 +1519,13 @@ shapeFunc res x y = if and [ findMaxOrdFor (ymandel (x+xeps) (y+yeps)) 0 1 20 < 0.000001 | val <- [res], xeps <- [-val,val], yeps<-[-val,val]] then Nothing - else Just (z,colorFromValue ((ymandel x y z) * 64)) + else Just (z,colorFromValue 0) colorFromValue :: Point -> Color colorFromValue n = let t :: Point -> Scalar - t i = 0.7 + 0.3*cos( i / 10 ) + t i = 0.0 + 0.5*cos( i /10 ) in makeColor (t n) (t (n+5)) (t (n+10)) @@ -1502,5 +1549,5 @@ ymandel x y z = fromIntegral (mandel x y z 64) / 64 - [`Mandel`](code/06_Mandelbulb/Mandel.hs), the mandel function - [`ExtComplex`](code/06_Mandelbulb/ExtComplex.hs), the extended complexes -06_Mandelbulb/Mandelbulb.lhs +Download the source code of this section → 06_Mandelbulb/Mandelbulb.lhs diff --git a/multi/blog/Haskell-OpenGL-Mandelbrot.md b/multi/blog/Haskell-OpenGL-Mandelbrot.md index 8caefdebd..a8e1e5a76 100644 --- a/multi/blog/Haskell-OpenGL-Mandelbrot.md +++ b/multi/blog/Haskell-OpenGL-Mandelbrot.md @@ -15,12 +15,12 @@ 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. >

Table of Content
@@ -33,46 +33,63 @@ enddiv ## 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. -Steps: +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. 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 +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`). + +I believe the main audience for this article are: + +- 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! +We start cleaning everything at the 4th part. -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. - -
01_Introduction/hglmandel.lhs +
Download the source code of this section → 01_Introduction/hglmandel.lhs ## 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. @@ -129,9 +146,6 @@ magnitude = real.abs ### 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:
@@ -152,7 +166,8 @@ main = do
-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:
@@ -164,12 +179,12 @@ display = do
-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: @@ -202,8 +217,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.
@@ -238,7 +253,7 @@ colorFromValue n =
-And now the mandel function. +And now the `mandel` function. Given two coordinates in pixels, it returns some integer value:
@@ -251,8 +266,8 @@ mandel x y =
-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}\\) @@ -274,15 +289,15 @@ f c z n = if (magnitude z > 2 )
-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. @@ -291,14 +306,13 @@ 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. -01_Introduction/hglmandel.lhs +Download the source code of this section → 01_Introduction/hglmandel.lhs -
02_Edges/HGLMandelEdge.lhs +
Download the source code of this section → 02_Edges/HGLMandelEdge.lhs ## Only the edges @@ -356,6 +370,10 @@ height = 320 :: GLfloat
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` @@ -386,21 +404,23 @@ allPoints = 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 - if y < 1 -- We don't draw point in the absciss - then [] - else return (x/width,y/height,colorFromValue $ mandel x y) + x <- [-width..width] + 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: ~~~ @@ -412,7 +432,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:
@@ -425,9 +445,7 @@ findMaxOrdFor func minval maxval n =
-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") @@ -466,27 +484,28 @@ f c z n = if (magnitude z > 2 ) -02_Edges/HGLMandelEdge.lhs +Download the source code of this section → 02_Edges/HGLMandelEdge.lhs -
03_Mandelbulb/Mandelbulb.lhs +
Download the source code of this section → 03_Mandelbulb/Mandelbulb.lhs ## 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... @@ -523,8 +542,8 @@ type ColoredPoint = (GLfloat,GLfloat,GLfloat,Color3 GLfloat) -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:
@@ -545,7 +564,17 @@ instance Num ExtComplex where
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.
@@ -588,15 +617,14 @@ main = do 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 @@ -608,7 +636,8 @@ main = do
-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.
@@ -618,6 +647,9 @@ 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)`
@@ -633,25 +665,29 @@ 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))) @@ -662,9 +698,8 @@ keyboardMouse angle zoom pos key state modifiers position =
-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:
@@ -684,9 +719,11 @@ display angle zoom position = do (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
@@ -696,9 +733,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.
@@ -711,7 +748,8 @@ deep = nbDetails 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.
@@ -726,14 +764,13 @@ drawMandelbrot = do
-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 @@ -753,7 +790,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) @@ -762,10 +800,10 @@ depthPoints = do c2 = mandel (x+1) y (z2+1) c3 = mandel (x+1) (y+1) (z3+1) c4 = mandel x (y+1) (z4+1) - p1 = ( x /width, y /height, z1/deep,colorFromValue c1) - p2 = ((x+1)/width, y /height, z2/deep,colorFromValue c2) - p3 = ((x+1)/width,(y+1)/height, z3/deep,colorFromValue c3) - p4 = ( x /width,(y+1)/height, z4/deep,colorFromValue c4) + p1 = ( x /width, y /height, z1/deep, colorFromValue c1) + p2 = ((x+1)/width, y /height, z2/deep, colorFromValue c2) + p3 = ((x+1)/width,(y+1)/height, z3/deep, colorFromValue c3) + p4 = ( x /width,(y+1)/height, z4/deep, colorFromValue c4) if (and $ map (>=57) [c1,c2,c3,c4]) then [] else [p1,p2,p3,p1,p3,p4] @@ -773,17 +811,18 @@ 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 @@ -802,26 +841,21 @@ depthPoints = do 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.
@@ -863,7 +897,8 @@ f c z n = if (magnitude z > 2 )
-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`.
@@ -876,21 +911,19 @@ mandel x y z =
-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. +Download the source code of this section → 03_Mandelbulb/Mandelbulb.lhs -03_Mandelbulb/Mandelbulb.lhs +
Download the source code of this section → 04_Mandelbulb/Mandelbulb.lhs -
04_Mandelbulb/Mandelbulb.lhs - -## 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 @@ -987,13 +1020,10 @@ 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. +Download the source code of this section → 04_Mandelbulb/Mandelbulb.lhs -04_Mandelbulb/Mandelbulb.lhs - -
05_Mandelbulb/Mandelbulb.lhs +
Download the source code of this section → 05_Mandelbulb/Mandelbulb.lhs ## Functional organization? @@ -1015,7 +1045,7 @@ Some points: Then here is how I imagine things should go. First, what the main loop should look like: - + functionalMainLoop = Read user inputs and provide a list of actions Apply all actions to the World @@ -1272,16 +1302,16 @@ This file is commented a lot. - [`Mandel`](code/05_Mandelbulb/Mandel.hs), the mandel function - [`ExtComplex`](code/05_Mandelbulb/ExtComplex.hs), the extended complexes -05_Mandelbulb/Mandelbulb.lhs +Download the source code of this section → 05_Mandelbulb/Mandelbulb.lhs -
06_Mandelbulb/Mandelbulb.lhs +
Download the source code of this section → 06_Mandelbulb/Mandelbulb.lhs ## Optimization All feel good from the architecture point of vue. More precisely, the separation between rendering and world behavior is clear. But this is extremely slow now. -Because we compute the mandelbulb for each frame now. +Because we compute the Mandelbulb for each frame now. Before we had @@ -1311,7 +1341,8 @@ import Mandel -- The 3D Mandelbrot maths -- Centralize all user input interaction inputActionMap :: InputMap World inputActionMap = inputMapFromList [ - (Press 'k' , rotate xdir 5) + (Press ' ' , switch_rotation) + ,(Press 'k' , rotate xdir 5) ,(Press 'i' , rotate xdir (-5)) ,(Press 'j' , rotate ydir 5) ,(Press 'l' , rotate ydir (-5)) @@ -1325,8 +1356,8 @@ inputActionMap = inputMapFromList [ ,(Press 'r' , translate zdir (-0.1)) ,(Press '+' , zoom 1.1) ,(Press '-' , zoom (1/1.1)) - ,(Press 'h' , resize 1.2) - ,(Press 'g' , resize (1/1.2)) + ,(Press 'h' , resize 2.0) + ,(Press 'g' , resize (1/2.0)) ]
@@ -1337,6 +1368,7 @@ inputActionMap = inputMapFromList [ data World = World { angle :: Point3D + , anglePerSec :: Scalar , scale :: Scalar , position :: Point3D , box :: Box3D @@ -1376,6 +1408,11 @@ rotate dir angleValue world = world { angle = (angle world) + (angleValue -*< dir) } +switch_rotation :: World -> World +switch_rotation world = + world { + anglePerSec = if anglePerSec world > 0 then 0 else 5.0 } + translate :: Point3D -> Scalar -> World -> World translate dir len world = world { @@ -1406,11 +1443,12 @@ Our initial world state is slightly changed: initialWorld :: World initialWorld = World { angle = makePoint3D (30,30,0) + , anglePerSec = 5.0 , position = makePoint3D (0,0,0) , scale = 1.0 , box = Box3D { minPoint = makePoint3D (-2,-2,-2) , maxPoint = makePoint3D (2,2,2) - , resolution = 0.02 } + , resolution = 0.03 } , told = 0 -- We declare cache directly this time , cache = objectFunctionFromWorld initialWorld @@ -1426,11 +1464,12 @@ This way instead of providing `XYFunc`, we provide directly a list of Atoms. objectFunctionFromWorld :: World -> [YObject] objectFunctionFromWorld w = [Atoms atomList] where atomListPositive = - getObject3DFromShapeFunction (shapeFunc (resolution (box w))) (box w) + getObject3DFromShapeFunction + (shapeFunc (resolution (box w))) (box w) atomList = atomListPositive ++ map negativeTriangle atomListPositive negativeTriangle (ColoredTriangle (p1,p2,p3,c)) = - ColoredTriangle (negz p1,negz p2,negz p3,c) + ColoredTriangle (negz p1,negz p3,negz p2,c) where negz (P (x,y,z)) = P (x,y,-z)
@@ -1463,10 +1502,18 @@ idleAction tnew world = , told = tnew } where - anglePerSec = 5.0 - delta = anglePerSec * elapsed / 1000.0 + delta = anglePerSec world * elapsed / 1000.0 elapsed = fromIntegral (tnew - (told world)) +shapeFunc' :: Scalar -> Function3D +shapeFunc' res x y = if or [tmp u v>=0 | u<-[x,x+res], v<-[y,y+res]] + then Just (z,hexColor "#AD4") + else Nothing + where tmp x y = (x**2 + y**2) + protectSqrt t = if t<0 then 0 else sqrt t + z = sqrt (a**2 - (c - protectSqrt(tmp x y))**2) + a = 0.2 + c = 0.5 shapeFunc :: Scalar -> Function3D shapeFunc res x y = let @@ -1475,13 +1522,13 @@ shapeFunc res x y = if and [ findMaxOrdFor (ymandel (x+xeps) (y+yeps)) 0 1 20 < 0.000001 | val <- [res], xeps <- [-val,val], yeps<-[-val,val]] then Nothing - else Just (z,colorFromValue ((ymandel x y z) * 64)) + else Just (z,colorFromValue 0) colorFromValue :: Point -> Color colorFromValue n = let t :: Point -> Scalar - t i = 0.7 + 0.3*cos( i / 10 ) + t i = 0.0 + 0.5*cos( i /10 ) in makeColor (t n) (t (n+5)) (t (n+10)) @@ -1505,5 +1552,5 @@ ymandel x y z = fromIntegral (mandel x y z 64) / 64 - [`Mandel`](code/06_Mandelbulb/Mandel.hs), the mandel function - [`ExtComplex`](code/06_Mandelbulb/ExtComplex.hs), the extended complexes -06_Mandelbulb/Mandelbulb.lhs +Download the source code of this section → 06_Mandelbulb/Mandelbulb.lhs diff --git a/output/Scratch/blank.html b/output/Scratch/blank.html new file mode 100644 index 000000000..e69de29bb diff --git a/output/Scratch/en/blog/2010-06-17-hide-yourself-to-analytics/code/become_hidden.html b/output/Scratch/en/blog/2010-06-17-hide-yourself-to-analytics/code/become_hidden.html new file mode 100644 index 000000000..946d8732a --- /dev/null +++ b/output/Scratch/en/blog/2010-06-17-hide-yourself-to-analytics/code/become_hidden.html @@ -0,0 +1,20 @@ + + + + + + + + + Hide to analytics + + +
+ + diff --git a/output/Scratch/en/blog/2010-06-17-hide-yourself-to-analytics/code/become_visible.html b/output/Scratch/en/blog/2010-06-17-hide-yourself-to-analytics/code/become_visible.html new file mode 100644 index 000000000..0f79658ee --- /dev/null +++ b/output/Scratch/en/blog/2010-06-17-hide-yourself-to-analytics/code/become_visible.html @@ -0,0 +1,20 @@ + + + + + + + + + Hide to analytics + + +
+ + diff --git a/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/00_Introduction.lhs b/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/00_Introduction.lhs index 4ca3b94e9..1075c6848 100644 --- a/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/00_Introduction.lhs +++ b/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/00_Introduction.lhs @@ -1,31 +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. -Steps: +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. 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 +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`). + +I believe the main audience for this article are: + +- 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. diff --git a/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/01_Introduction/hglmandel.lhs b/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/01_Introduction/hglmandel.lhs index c1da26866..41b653162 100644 --- a/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/01_Introduction/hglmandel.lhs +++ b/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/01_Introduction/hglmandel.lhs @@ -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. diff --git a/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/02_Edges/HGLMandelEdge.lhs b/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/02_Edges/HGLMandelEdge.lhs index 7b1da1787..b56a1f932 100644 --- a/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/02_Edges/HGLMandelEdge.lhs +++ b/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/02_Edges/HGLMandelEdge.lhs @@ -51,6 +51,10 @@
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 -> if y < 1 -- We don't draw point in the absciss -> then [] -> else return (x/width,y/height,colorFromValue $ mandel x y) +> x <- [-width..width] +> 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") diff --git a/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/03_Mandelbulb/Mandelbulb.lhs b/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/03_Mandelbulb/Mandelbulb.lhs index d26703a0b..59af425db 100644 --- a/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/03_Mandelbulb/Mandelbulb.lhs +++ b/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/03_Mandelbulb/Mandelbulb.lhs @@ -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:
-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.
@@ -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) @@ -252,10 +273,10 @@ depthPoints = do c2 = mandel (x+1) y (z2+1) c3 = mandel (x+1) (y+1) (z3+1) c4 = mandel x (y+1) (z4+1) - p1 = ( x /width, y /height, z1/deep,colorFromValue c1) - p2 = ((x+1)/width, y /height, z2/deep,colorFromValue c2) - p3 = ((x+1)/width,(y+1)/height, z3/deep,colorFromValue c3) - p4 = ( x /width,(y+1)/height, z4/deep,colorFromValue c4) + p1 = ( x /width, y /height, z1/deep, colorFromValue c1) + p2 = ((x+1)/width, y /height, z2/deep, colorFromValue c2) + p3 = ((x+1)/width,(y+1)/height, z3/deep, colorFromValue c3) + p4 = ( x /width,(y+1)/height, z4/deep, colorFromValue c4) if (and $ map (>=57) [c1,c2,c3,c4]) then [] else [p1,p2,p3,p1,p3,p4] @@ -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.
@@ -333,7 +350,8 @@ We only changed from `Complex` to `ExtComplex` of the main `f` function.
-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. - diff --git a/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/Mandelbulb.lhs b/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/Mandelbulb.lhs index 30ed4d98f..270eab67b 100644 --- a/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/Mandelbulb.lhs +++ b/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/Mandelbulb.lhs @@ -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. diff --git a/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/Mandelbulb.lhs b/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/Mandelbulb.lhs index 12e0e0586..8ebc89fd0 100644 --- a/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/Mandelbulb.lhs +++ b/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/Mandelbulb.lhs @@ -18,7 +18,7 @@ Some points: Then here is how I imagine things should go. First, what the main loop should look like: - + functionalMainLoop = Read user inputs and provide a list of actions Apply all actions to the World diff --git a/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/YGL.hs b/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/YGL.hs index b4fd80847..b1b21dde4 100644 --- a/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/YGL.hs +++ b/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/YGL.hs @@ -227,10 +227,11 @@ yMainLoop inputActionMap Just (keyboardMouse inputActionMap worldRef) -- We generate one frame using the callback displayCallback $= display worldRef + normalize $= Enabled -- Lights lighting $= Enabled ambient (Light 0) $= Color4 0 0 0 1 - diffuse (Light 0) $= Color4 1 1 1 1 + diffuse (Light 0) $= Color4 0.5 0.5 0.5 1 specular (Light 0) $= Color4 1 1 1 1 position (Light 0) $= Vertex4 1 1 0 1 light (Light 0) $= Enabled @@ -239,7 +240,7 @@ yMainLoop inputActionMap materialAmbient Front $= Color4 0.5 0.5 0.5 1 materialSpecular Front $= Color4 0.2 0.2 0.2 1 materialEmission Front $= Color4 0.3 0.3 0.3 1 - materialShininess Front $= 50.0 + materialShininess Front $= 90.0 -- We enter the main loop mainLoop diff --git a/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/ExtComplex.hs b/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/ExtComplex.hs index 6500028d9..66861b92f 100644 --- a/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/ExtComplex.hs +++ b/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/ExtComplex.hs @@ -21,13 +21,13 @@ extcomplex :: GLfloat -> GLfloat -> GLfloat -> ExtComplex extcomplex x y z = C (x,y,z) real :: ExtComplex -> GLfloat -real (C (x,y,z)) = x +real (C (x,_,_)) = x im :: ExtComplex -> GLfloat -im (C (x,y,z)) = y +im (C (_,y,_)) = y strange :: ExtComplex -> GLfloat -strange (C (x,y,z)) = z +strange (C (_,_,z)) = z magnitude :: ExtComplex -> GLfloat magnitude = real.abs diff --git a/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/Mandel.hs b/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/Mandel.hs index 9500e0bc6..3997a83e0 100644 --- a/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/Mandel.hs +++ b/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/Mandel.hs @@ -7,7 +7,7 @@ mandel r i s nbIterations = f (extcomplex r i s) 0 nbIterations where f :: ExtComplex -> ExtComplex -> Int -> Int - f c z 0 = 0 + f _ _ 0 = 0 f c z n = if (magnitude z > 2 ) then n else f c ((z*z)+c) (n-1) diff --git a/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/Mandelbulb.lhs b/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/Mandelbulb.lhs index 64284474c..bfad62d57 100644 --- a/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/Mandelbulb.lhs +++ b/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/Mandelbulb.lhs @@ -3,7 +3,7 @@ All feel good from the architecture point of vue. More precisely, the separation between rendering and world behavior is clear. But this is extremely slow now. -Because we compute the mandelbulb for each frame now. +Because we compute the Mandelbulb for each frame now. Before we had @@ -31,7 +31,8 @@ function, we will provide the list of atoms directly. > -- Centralize all user input interaction > inputActionMap :: InputMap World > inputActionMap = inputMapFromList [ -> (Press 'k' , rotate xdir 5) +> (Press ' ' , switch_rotation) +> ,(Press 'k' , rotate xdir 5) > ,(Press 'i' , rotate xdir (-5)) > ,(Press 'j' , rotate ydir 5) > ,(Press 'l' , rotate ydir (-5)) @@ -45,14 +46,15 @@ function, we will provide the list of atoms directly. > ,(Press 'r' , translate zdir (-0.1)) > ,(Press '+' , zoom 1.1) > ,(Press '-' , zoom (1/1.1)) -> ,(Press 'h' , resize 1.2) -> ,(Press 'g' , resize (1/1.2)) +> ,(Press 'h' , resize 2.0) +> ,(Press 'g' , resize (1/2.0)) > ]
> data World = World { > angle :: Point3D +> , anglePerSec :: Scalar > , scale :: Scalar > , position :: Point3D > , box :: Box3D @@ -84,6 +86,11 @@ function, we will provide the list of atoms directly. > rotate dir angleValue world = > world { > angle = (angle world) + (angleValue -*< dir) } +> +> switch_rotation :: World -> World +> switch_rotation world = +> world { +> anglePerSec = if anglePerSec world > 0 then 0 else 5.0 } > > translate :: Point3D -> Scalar -> World -> World > translate dir len world = @@ -108,11 +115,12 @@ Our initial world state is slightly changed: > initialWorld :: World > initialWorld = World { > angle = makePoint3D (30,30,0) +> , anglePerSec = 5.0 > , position = makePoint3D (0,0,0) > , scale = 1.0 > , box = Box3D { minPoint = makePoint3D (-2,-2,-2) > , maxPoint = makePoint3D (2,2,2) -> , resolution = 0.02 } +> , resolution = 0.03 } > , told = 0 > -- We declare cache directly this time > , cache = objectFunctionFromWorld initialWorld @@ -124,11 +132,12 @@ This way instead of providing `XYFunc`, we provide directly a list of Atoms. > objectFunctionFromWorld :: World -> [YObject] > objectFunctionFromWorld w = [Atoms atomList] > where atomListPositive = -> getObject3DFromShapeFunction (shapeFunc (resolution (box w))) (box w) +> getObject3DFromShapeFunction +> (shapeFunc (resolution (box w))) (box w) > atomList = atomListPositive ++ > map negativeTriangle atomListPositive > negativeTriangle (ColoredTriangle (p1,p2,p3,c)) = -> ColoredTriangle (negz p1,negz p2,negz p3,c) +> ColoredTriangle (negz p1,negz p3,negz p2,c) > where negz (P (x,y,z)) = P (x,y,-z) We know that resize is the only world change that necessitate to @@ -153,10 +162,18 @@ All the rest is exactly the same. > , told = tnew > } > where -> anglePerSec = 5.0 -> delta = anglePerSec * elapsed / 1000.0 +> delta = anglePerSec world * elapsed / 1000.0 > elapsed = fromIntegral (tnew - (told world)) > +> shapeFunc' :: Scalar -> Function3D +> shapeFunc' res x y = if or [tmp u v>=0 | u<-[x,x+res], v<-[y,y+res]] +> then Just (z,hexColor "#AD4") +> else Nothing +> where tmp x y = (x**2 + y**2) +> protectSqrt t = if t<0 then 0 else sqrt t +> z = sqrt (a**2 - (c - protectSqrt(tmp x y))**2) +> a = 0.2 +> c = 0.5 > shapeFunc :: Scalar -> Function3D > shapeFunc res x y = > let @@ -165,13 +182,13 @@ All the rest is exactly the same. > if and [ findMaxOrdFor (ymandel (x+xeps) (y+yeps)) 0 1 20 < 0.000001 | > val <- [res], xeps <- [-val,val], yeps<-[-val,val]] > then Nothing -> else Just (z,colorFromValue ((ymandel x y z) * 64)) +> else Just (z,colorFromValue 0) > > colorFromValue :: Point -> Color > colorFromValue n = > let > t :: Point -> Scalar -> t i = 0.7 + 0.3*cos( i / 10 ) +> t i = 0.0 + 0.5*cos( i /10 ) > in > makeColor (t n) (t (n+5)) (t (n+10)) > diff --git a/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/YGL.hs b/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/YGL.hs index a645809ad..f71e49f52 100644 --- a/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/YGL.hs +++ b/output/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/YGL.hs @@ -70,8 +70,7 @@ zpoint :: Point3D -> Point zpoint (P (_,_,z)) = z makePoint3D :: (Point,Point,Point) -> Point3D -makePoint3D p = P p - +makePoint3D = P instance Num Point3D where (+) (P (ax,ay,az)) (P (bx,by,bz)) = P (ax+bx,ay+by,az+bz) @@ -230,26 +229,24 @@ yMainLoop inputActionMap Just (keyboardMouse inputActionMap worldRef) -- We generate one frame using the callback displayCallback $= display worldRef + normalize $= Enabled -- let OpenGL resize normal vectors to unity + shadeModel $= Smooth -- Lights lighting $= Enabled - ambient (Light 0) $= Color4 0 0 0 1 + ambient (Light 0) $= Color4 0.5 0.5 0.5 1 diffuse (Light 0) $= Color4 1 1 1 1 - specular (Light 0) $= Color4 1 1 1 1 - position (Light 0) $= Vertex4 1 1 0 1 + -- specular (Light 0) $= Color4 1 1 1 1 + -- position (Light 0) $= Vertex4 (-5) 5 10 0 light (Light 0) $= Enabled - ambient (Light 1) $= Color4 0 0 0 1 - diffuse (Light 1) $= Color4 1 0.9 0.0 1 - specular (Light 1) $= Color4 1 1 1 1 - position (Light 1) $= Vertex4 0 0 1 1 - light (Light 1) $= Enabled + pointSmooth $= Enabled + colorMaterial $= Just (Front,AmbientAndDiffuse) - -- materialDiffuse Front $= Color4 0.5 0.5 0.5 1 - materialDiffuse Front $= Color4 0.5 0.5 0.5 1 - materialAmbient Front $= Color4 0.5 0.5 0.5 1 - materialSpecular Front $= Color4 0.2 0.2 0.2 1 - materialEmission Front $= Color4 0.3 0.3 0.3 1 - materialShininess Front $= 1.0 + materialAmbient Front $= Color4 0.0 0.0 0.0 1 + materialDiffuse Front $= Color4 0.0 0.0 0.0 1 + materialSpecular Front $= Color4 1 1 1 1 + materialEmission Front $= Color4 0.0 0.0 0.0 1 -- We enter the main loop + materialShininess Front $= 96 mainLoop -- When no user input entered do nothing @@ -317,25 +314,21 @@ display worldRef = do scalarFromHex :: String -> Scalar scalarFromHex = (/256) . fst . head . readHex -hexColor :: [Char] -> Color -hexColor ('#':rd:ru:gd:gu:bd:bu:[]) = Color3 (scalarFromHex (rd:ru:[])) - (scalarFromHex (gd:gu:[])) - (scalarFromHex (bd:bu:[])) -hexColor ('#':r:g:b:[]) = hexColor ('#':r:r:g:g:b:b:[]) +hexColor :: String -> Color +hexColor ('#':rd:ru:gd:gu:bd:bu:[]) = Color3 (scalarFromHex [rd,ru]) + (scalarFromHex [gd,gu]) + (scalarFromHex [bd,bu]) +hexColor ('#':r:g:b:[]) = hexColor ['#',r,r,g,g,b,b] hexColor _ = error "Bad color!!!!" makeColor :: Scalar -> Scalar -> Scalar -> Color -makeColor x y z = Color3 x y z +makeColor = Color3 --- -- drawObject :: (YObject obj) => obj -> IO() drawObject :: YObject -> IO() -drawObject shape = do - -- We will print only Triangles - renderPrimitive Triangles $ do - -- solarized base3 color - -- color $ hexColor "#fdf603" - mapM_ drawAtom (atoms shape) +drawObject shape = renderPrimitive Triangles $ + mapM_ drawAtom (atoms shape) -- simply draw an Atom drawAtom :: Atom -> IO () diff --git a/output/Scratch/en/blog/feed/feed.xml b/output/Scratch/en/blog/feed/feed.xml index 705ad2648..ad16a7048 100644 --- a/output/Scratch/en/blog/feed/feed.xml +++ b/output/Scratch/en/blog/feed/feed.xml @@ -19,13 +19,13 @@ yannesposito.com - <p><img alt="The plan in image" src="/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/HGL_Plan.png" /></p> + <p><img alt="The B in Benoît B. Mandelbrot stand for Benoît B. Mandelbrot" src="/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/BenoitBMandelbrot.jpg" /></p> <div class="intro"> -<p><span class="sc"><abbr title="Too long; didn't read">tl;dr</abbr>: </span> A progressive real world example.</p> +<p><span class="sc"><abbr title="Too long; didn't read">tl;dr</abbr>: </span> You will see how to go from theory to a real application using Haskell.</p> <blockquote> <center><hr style="width:30%;float:left;border-color:#CCCCD0;margin-top:1em" /><span class="sc"><b>Table of Content</b></span><hr style="width:30%;float:right;border-color:#CCCCD0;margin-top:1em" /></center> @@ -34,10 +34,7 @@ <li><a href="#introduction">Introduction</a></li> <li><a href="#first-version">First version</a> <ul> <li><a href="#lets-play-the-song-of-our-people">Let&rsquo;s play the song of our people</a></li> - <li><a href="#let-us-start">Let us start</a></li> - </ul> - </li> - <li></li></ul></hr></center></blockquote></div></p> + ...</ul></li></ul></hr></center></blockquote></div></p> tag:yannesposito.com,2012-02-08:/Scratch/en/blog/Haskell-the-Hard-Way/ diff --git a/output/Scratch/fr/blog/2010-06-17-hide-yourself-to-analytics/code/become_hidden.html b/output/Scratch/fr/blog/2010-06-17-hide-yourself-to-analytics/code/become_hidden.html new file mode 100644 index 000000000..946d8732a --- /dev/null +++ b/output/Scratch/fr/blog/2010-06-17-hide-yourself-to-analytics/code/become_hidden.html @@ -0,0 +1,20 @@ + + + + + + + + + Hide to analytics + + +
+ + diff --git a/output/Scratch/fr/blog/2010-06-17-hide-yourself-to-analytics/code/become_visible.html b/output/Scratch/fr/blog/2010-06-17-hide-yourself-to-analytics/code/become_visible.html new file mode 100644 index 000000000..0f79658ee --- /dev/null +++ b/output/Scratch/fr/blog/2010-06-17-hide-yourself-to-analytics/code/become_visible.html @@ -0,0 +1,20 @@ + + + + + + + + + Hide to analytics + + +
+ + diff --git a/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/00_Introduction.lhs b/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/00_Introduction.lhs index 4ca3b94e9..1075c6848 100644 --- a/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/00_Introduction.lhs +++ b/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/00_Introduction.lhs @@ -1,31 +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. -Steps: +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. 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 +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`). + +I believe the main audience for this article are: + +- 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. diff --git a/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/01_Introduction/hglmandel.lhs b/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/01_Introduction/hglmandel.lhs index c1da26866..41b653162 100644 --- a/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/01_Introduction/hglmandel.lhs +++ b/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/01_Introduction/hglmandel.lhs @@ -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. diff --git a/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/02_Edges/HGLMandelEdge.lhs b/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/02_Edges/HGLMandelEdge.lhs index 7b1da1787..b56a1f932 100644 --- a/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/02_Edges/HGLMandelEdge.lhs +++ b/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/02_Edges/HGLMandelEdge.lhs @@ -51,6 +51,10 @@ 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 -> if y < 1 -- We don't draw point in the absciss -> then [] -> else return (x/width,y/height,colorFromValue $ mandel x y) +> x <- [-width..width] +> 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") diff --git a/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/03_Mandelbulb/Mandelbulb.lhs b/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/03_Mandelbulb/Mandelbulb.lhs index d26703a0b..59af425db 100644 --- a/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/03_Mandelbulb/Mandelbulb.lhs +++ b/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/03_Mandelbulb/Mandelbulb.lhs @@ -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: -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.
@@ -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) @@ -252,10 +273,10 @@ depthPoints = do c2 = mandel (x+1) y (z2+1) c3 = mandel (x+1) (y+1) (z3+1) c4 = mandel x (y+1) (z4+1) - p1 = ( x /width, y /height, z1/deep,colorFromValue c1) - p2 = ((x+1)/width, y /height, z2/deep,colorFromValue c2) - p3 = ((x+1)/width,(y+1)/height, z3/deep,colorFromValue c3) - p4 = ( x /width,(y+1)/height, z4/deep,colorFromValue c4) + p1 = ( x /width, y /height, z1/deep, colorFromValue c1) + p2 = ((x+1)/width, y /height, z2/deep, colorFromValue c2) + p3 = ((x+1)/width,(y+1)/height, z3/deep, colorFromValue c3) + p4 = ( x /width,(y+1)/height, z4/deep, colorFromValue c4) if (and $ map (>=57) [c1,c2,c3,c4]) then [] else [p1,p2,p3,p1,p3,p4] @@ -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.
@@ -333,7 +350,8 @@ We only changed from `Complex` to `ExtComplex` of the main `f` function.
-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. - diff --git a/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/Mandelbulb.lhs b/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/Mandelbulb.lhs index 30ed4d98f..270eab67b 100644 --- a/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/Mandelbulb.lhs +++ b/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/04_Mandelbulb/Mandelbulb.lhs @@ -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. diff --git a/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/Mandelbulb.lhs b/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/Mandelbulb.lhs index 12e0e0586..8ebc89fd0 100644 --- a/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/Mandelbulb.lhs +++ b/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/Mandelbulb.lhs @@ -18,7 +18,7 @@ Some points: Then here is how I imagine things should go. First, what the main loop should look like: - + functionalMainLoop = Read user inputs and provide a list of actions Apply all actions to the World diff --git a/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/YGL.hs b/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/YGL.hs index b4fd80847..b1b21dde4 100644 --- a/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/YGL.hs +++ b/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/05_Mandelbulb/YGL.hs @@ -227,10 +227,11 @@ yMainLoop inputActionMap Just (keyboardMouse inputActionMap worldRef) -- We generate one frame using the callback displayCallback $= display worldRef + normalize $= Enabled -- Lights lighting $= Enabled ambient (Light 0) $= Color4 0 0 0 1 - diffuse (Light 0) $= Color4 1 1 1 1 + diffuse (Light 0) $= Color4 0.5 0.5 0.5 1 specular (Light 0) $= Color4 1 1 1 1 position (Light 0) $= Vertex4 1 1 0 1 light (Light 0) $= Enabled @@ -239,7 +240,7 @@ yMainLoop inputActionMap materialAmbient Front $= Color4 0.5 0.5 0.5 1 materialSpecular Front $= Color4 0.2 0.2 0.2 1 materialEmission Front $= Color4 0.3 0.3 0.3 1 - materialShininess Front $= 50.0 + materialShininess Front $= 90.0 -- We enter the main loop mainLoop diff --git a/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/ExtComplex.hs b/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/ExtComplex.hs index 6500028d9..66861b92f 100644 --- a/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/ExtComplex.hs +++ b/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/ExtComplex.hs @@ -21,13 +21,13 @@ extcomplex :: GLfloat -> GLfloat -> GLfloat -> ExtComplex extcomplex x y z = C (x,y,z) real :: ExtComplex -> GLfloat -real (C (x,y,z)) = x +real (C (x,_,_)) = x im :: ExtComplex -> GLfloat -im (C (x,y,z)) = y +im (C (_,y,_)) = y strange :: ExtComplex -> GLfloat -strange (C (x,y,z)) = z +strange (C (_,_,z)) = z magnitude :: ExtComplex -> GLfloat magnitude = real.abs diff --git a/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/Mandel.hs b/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/Mandel.hs index 9500e0bc6..3997a83e0 100644 --- a/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/Mandel.hs +++ b/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/Mandel.hs @@ -7,7 +7,7 @@ mandel r i s nbIterations = f (extcomplex r i s) 0 nbIterations where f :: ExtComplex -> ExtComplex -> Int -> Int - f c z 0 = 0 + f _ _ 0 = 0 f c z n = if (magnitude z > 2 ) then n else f c ((z*z)+c) (n-1) diff --git a/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/Mandelbulb.lhs b/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/Mandelbulb.lhs index 64284474c..bfad62d57 100644 --- a/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/Mandelbulb.lhs +++ b/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/Mandelbulb.lhs @@ -3,7 +3,7 @@ All feel good from the architecture point of vue. More precisely, the separation between rendering and world behavior is clear. But this is extremely slow now. -Because we compute the mandelbulb for each frame now. +Because we compute the Mandelbulb for each frame now. Before we had @@ -31,7 +31,8 @@ function, we will provide the list of atoms directly. > -- Centralize all user input interaction > inputActionMap :: InputMap World > inputActionMap = inputMapFromList [ -> (Press 'k' , rotate xdir 5) +> (Press ' ' , switch_rotation) +> ,(Press 'k' , rotate xdir 5) > ,(Press 'i' , rotate xdir (-5)) > ,(Press 'j' , rotate ydir 5) > ,(Press 'l' , rotate ydir (-5)) @@ -45,14 +46,15 @@ function, we will provide the list of atoms directly. > ,(Press 'r' , translate zdir (-0.1)) > ,(Press '+' , zoom 1.1) > ,(Press '-' , zoom (1/1.1)) -> ,(Press 'h' , resize 1.2) -> ,(Press 'g' , resize (1/1.2)) +> ,(Press 'h' , resize 2.0) +> ,(Press 'g' , resize (1/2.0)) > ]
> data World = World { > angle :: Point3D +> , anglePerSec :: Scalar > , scale :: Scalar > , position :: Point3D > , box :: Box3D @@ -84,6 +86,11 @@ function, we will provide the list of atoms directly. > rotate dir angleValue world = > world { > angle = (angle world) + (angleValue -*< dir) } +> +> switch_rotation :: World -> World +> switch_rotation world = +> world { +> anglePerSec = if anglePerSec world > 0 then 0 else 5.0 } > > translate :: Point3D -> Scalar -> World -> World > translate dir len world = @@ -108,11 +115,12 @@ Our initial world state is slightly changed: > initialWorld :: World > initialWorld = World { > angle = makePoint3D (30,30,0) +> , anglePerSec = 5.0 > , position = makePoint3D (0,0,0) > , scale = 1.0 > , box = Box3D { minPoint = makePoint3D (-2,-2,-2) > , maxPoint = makePoint3D (2,2,2) -> , resolution = 0.02 } +> , resolution = 0.03 } > , told = 0 > -- We declare cache directly this time > , cache = objectFunctionFromWorld initialWorld @@ -124,11 +132,12 @@ This way instead of providing `XYFunc`, we provide directly a list of Atoms. > objectFunctionFromWorld :: World -> [YObject] > objectFunctionFromWorld w = [Atoms atomList] > where atomListPositive = -> getObject3DFromShapeFunction (shapeFunc (resolution (box w))) (box w) +> getObject3DFromShapeFunction +> (shapeFunc (resolution (box w))) (box w) > atomList = atomListPositive ++ > map negativeTriangle atomListPositive > negativeTriangle (ColoredTriangle (p1,p2,p3,c)) = -> ColoredTriangle (negz p1,negz p2,negz p3,c) +> ColoredTriangle (negz p1,negz p3,negz p2,c) > where negz (P (x,y,z)) = P (x,y,-z) We know that resize is the only world change that necessitate to @@ -153,10 +162,18 @@ All the rest is exactly the same. > , told = tnew > } > where -> anglePerSec = 5.0 -> delta = anglePerSec * elapsed / 1000.0 +> delta = anglePerSec world * elapsed / 1000.0 > elapsed = fromIntegral (tnew - (told world)) > +> shapeFunc' :: Scalar -> Function3D +> shapeFunc' res x y = if or [tmp u v>=0 | u<-[x,x+res], v<-[y,y+res]] +> then Just (z,hexColor "#AD4") +> else Nothing +> where tmp x y = (x**2 + y**2) +> protectSqrt t = if t<0 then 0 else sqrt t +> z = sqrt (a**2 - (c - protectSqrt(tmp x y))**2) +> a = 0.2 +> c = 0.5 > shapeFunc :: Scalar -> Function3D > shapeFunc res x y = > let @@ -165,13 +182,13 @@ All the rest is exactly the same. > if and [ findMaxOrdFor (ymandel (x+xeps) (y+yeps)) 0 1 20 < 0.000001 | > val <- [res], xeps <- [-val,val], yeps<-[-val,val]] > then Nothing -> else Just (z,colorFromValue ((ymandel x y z) * 64)) +> else Just (z,colorFromValue 0) > > colorFromValue :: Point -> Color > colorFromValue n = > let > t :: Point -> Scalar -> t i = 0.7 + 0.3*cos( i / 10 ) +> t i = 0.0 + 0.5*cos( i /10 ) > in > makeColor (t n) (t (n+5)) (t (n+10)) > diff --git a/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/YGL.hs b/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/YGL.hs index a645809ad..f71e49f52 100644 --- a/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/YGL.hs +++ b/output/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/code/06_Mandelbulb/YGL.hs @@ -70,8 +70,7 @@ zpoint :: Point3D -> Point zpoint (P (_,_,z)) = z makePoint3D :: (Point,Point,Point) -> Point3D -makePoint3D p = P p - +makePoint3D = P instance Num Point3D where (+) (P (ax,ay,az)) (P (bx,by,bz)) = P (ax+bx,ay+by,az+bz) @@ -230,26 +229,24 @@ yMainLoop inputActionMap Just (keyboardMouse inputActionMap worldRef) -- We generate one frame using the callback displayCallback $= display worldRef + normalize $= Enabled -- let OpenGL resize normal vectors to unity + shadeModel $= Smooth -- Lights lighting $= Enabled - ambient (Light 0) $= Color4 0 0 0 1 + ambient (Light 0) $= Color4 0.5 0.5 0.5 1 diffuse (Light 0) $= Color4 1 1 1 1 - specular (Light 0) $= Color4 1 1 1 1 - position (Light 0) $= Vertex4 1 1 0 1 + -- specular (Light 0) $= Color4 1 1 1 1 + -- position (Light 0) $= Vertex4 (-5) 5 10 0 light (Light 0) $= Enabled - ambient (Light 1) $= Color4 0 0 0 1 - diffuse (Light 1) $= Color4 1 0.9 0.0 1 - specular (Light 1) $= Color4 1 1 1 1 - position (Light 1) $= Vertex4 0 0 1 1 - light (Light 1) $= Enabled + pointSmooth $= Enabled + colorMaterial $= Just (Front,AmbientAndDiffuse) - -- materialDiffuse Front $= Color4 0.5 0.5 0.5 1 - materialDiffuse Front $= Color4 0.5 0.5 0.5 1 - materialAmbient Front $= Color4 0.5 0.5 0.5 1 - materialSpecular Front $= Color4 0.2 0.2 0.2 1 - materialEmission Front $= Color4 0.3 0.3 0.3 1 - materialShininess Front $= 1.0 + materialAmbient Front $= Color4 0.0 0.0 0.0 1 + materialDiffuse Front $= Color4 0.0 0.0 0.0 1 + materialSpecular Front $= Color4 1 1 1 1 + materialEmission Front $= Color4 0.0 0.0 0.0 1 -- We enter the main loop + materialShininess Front $= 96 mainLoop -- When no user input entered do nothing @@ -317,25 +314,21 @@ display worldRef = do scalarFromHex :: String -> Scalar scalarFromHex = (/256) . fst . head . readHex -hexColor :: [Char] -> Color -hexColor ('#':rd:ru:gd:gu:bd:bu:[]) = Color3 (scalarFromHex (rd:ru:[])) - (scalarFromHex (gd:gu:[])) - (scalarFromHex (bd:bu:[])) -hexColor ('#':r:g:b:[]) = hexColor ('#':r:r:g:g:b:b:[]) +hexColor :: String -> Color +hexColor ('#':rd:ru:gd:gu:bd:bu:[]) = Color3 (scalarFromHex [rd,ru]) + (scalarFromHex [gd,gu]) + (scalarFromHex [bd,bu]) +hexColor ('#':r:g:b:[]) = hexColor ['#',r,r,g,g,b,b] hexColor _ = error "Bad color!!!!" makeColor :: Scalar -> Scalar -> Scalar -> Color -makeColor x y z = Color3 x y z +makeColor = Color3 --- -- drawObject :: (YObject obj) => obj -> IO() drawObject :: YObject -> IO() -drawObject shape = do - -- We will print only Triangles - renderPrimitive Triangles $ do - -- solarized base3 color - -- color $ hexColor "#fdf603" - mapM_ drawAtom (atoms shape) +drawObject shape = renderPrimitive Triangles $ + mapM_ drawAtom (atoms shape) -- simply draw an Atom drawAtom :: Atom -> IO () diff --git a/output/Scratch/fr/blog/feed/feed.xml b/output/Scratch/fr/blog/feed/feed.xml index ac2988596..b57554474 100644 --- a/output/Scratch/fr/blog/feed/feed.xml +++ b/output/Scratch/fr/blog/feed/feed.xml @@ -19,13 +19,13 @@ yannesposito.com - <p><img alt="The plan in image" src="/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/HGL_Plan.png" /></p> + <p><img alt="The B in Benoît B. Mandelbrot stand for Benoît B. Mandelbrot" src="/Scratch/img/blog/Haskell-OpenGL-Mandelbrot/BenoitBMandelbrot.jpg" /></p> <div class="intro"> -<p><span class="sc"><abbr title="Trop long à lire">tlàl</abbr>&nbsp;: </span> Un exemple progressif de programmation avec Haskell.</p> +<p><span class="sc"><abbr title="Trop long à lire">tlàl</abbr>&nbsp;: </span> Un exemple progressif d&rsquo;utilisation d&rsquo;Haskell.</p> <blockquote> <center><hr style="width:30%;float:left;border-color:#CCCCD0;margin-top:1em" /><span class="sc"><b>Table of Content</b></span><hr style="width:30%;float:right;border-color:#CCCCD0;margin-top:1em" /></center> @@ -34,10 +34,7 @@ <li><a href="#introduction">Introduction</a></li> <li><a href="#first-version">First version</a> <ul> <li><a href="#lets-play-the-song-of-our-people">Let&rsquo;s play the song of our people</a></li> - <li><a href="#let-us-start">Let us start</a></li> - </ul> - </li> -...</ul></hr></center></blockquote></div></p> + <li></li></ul></li></ul></hr></center></blockquote></div></p>
tag:yannesposito.com,2012-02-08:/Scratch/fr/blog/Haskell-the-Hard-Way/ diff --git a/output/Scratch/js/become_hidden.html b/output/Scratch/js/become_hidden.html new file mode 100644 index 000000000..b619c6b3b --- /dev/null +++ b/output/Scratch/js/become_hidden.html @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + Hide to analytics + + +
+
+

+ Hide to Analytics +

+
+
+
+
+
+
+
+
+
+ + diff --git a/output/Scratch/js/become_visible.html b/output/Scratch/js/become_visible.html new file mode 100644 index 000000000..b21f53fb4 --- /dev/null +++ b/output/Scratch/js/become_visible.html @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + Be visible to analytics + + +
+
+

+ Be visible to Analytics +

+
+
+
+
+
+
+
+
+
+ + diff --git a/output/Scratch/js/highlight/export.html b/output/Scratch/js/highlight/export.html new file mode 100644 index 000000000..86ac89284 --- /dev/null +++ b/output/Scratch/js/highlight/export.html @@ -0,0 +1,87 @@ + + + + + + + + Highlited code export + + + + + + + + + + + + + + + + + + +
Write a code snippetGet HTML to paste anywhere (for actual styles and colors see sample.css)
+ + + + + +
+
+
+ Export script: Vladimir Gubarkov
+ Highlighting: highlight.js +
+ + diff --git a/output/Scratch/js/highlight/test.html b/output/Scratch/js/highlight/test.html new file mode 100644 index 000000000..5e7e2bdd6 --- /dev/null +++ b/output/Scratch/js/highlight/test.html @@ -0,0 +1,1845 @@ + + + highlight.js test + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

This is a demo/test page showing all languages supported by highlight.js. +Most snippets do not contain working code :-). + +

+

Styles

+
+ +

Automatically detected languages

+ +

...

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Python + +
+@requires_authorization
+def somefunc(param1='', param2=0):
+    r'''A docstring'''
+    if param1 > param2: # interesting
+        print 'Gre\'ater'
+    return (param2 - param1 + 1) or None
+
+class SomeClass:
pass +
+ +
Python's profiler output + +
       261917242 function calls in 686.251 CPU seconds
+
+       ncalls  tottime  filename:lineno(function)
+       152824  513.894  {method 'sort' of 'list' objects}
+    129590630   83.894  rrule.py:842(__cmp__)
+    129590630   82.439  {cmp}
+       153900    1.296  rrule.py:399(_iter)
+304393/151570    0.963  rrule.py:102(_iter_cached)
+
+ +
Ruby + +
class A < B; def self.create(object = User) object end end
+class Zebra; def inspect; "X#{2 + self.object_id}" end end
+
+module ABC::DEF
+  include Comparable
+
+  # @param test
+  # @return [String] nothing
+  def foo(test)
+    Thread.new do |blockvar|
+      ABC::DEF.reverse(:a_symbol, :'a symbol' + 'test' + test)
+    end.join
+  end
+
+  def [](index) self[index] end
+  def ==(other) other == self end
+end
+
+anIdentifier = an_identifier
+Constant = 1
+
+ +
Perl + +
# loads object
+sub load
+{
+  my $flds = $c->db_load($id,@_) || do {
+    Carp::carp "Can`t load (class: $c, id: $id): '$!'"; return undef
+  };
+  my $o = $c->_perl_new();
+  $id12 = $id / 24 / 3600;
+  $o->{'ID'} = $id12 + 123;
+  $o->{'PAPA'} = $flds->{'PAPA'};
+  #$o->{'SHCUT'} = $flds->{'SHCUT'};
+  my $p = $o->props;
+  my $vt;
+  $string =~ m/^sought_text$/;
+  $items = split //, 'abc';
+  for my $key (keys %$p)
+  {
+    if(${$vt.'::property'}) {
+      $o->{$key . '_real'} = $flds->{$key};
+      tie $o->{$key}, 'CMSBuilder::Property', $o, $key;
+    } else {
+      $o->{$key} = $flds->{$key};
+      $o->kill();
+    }
+  }
+  $o->save if delete $o->{'_save_after_load'};
+  return $o;
+}
+
+foreach my $num (0..$#array) {
+  # something
+}
+
+ +
PHP + +
require_once 'Zend.php';
+require_once 'Zend/Uri/Exception.php';
+require_once 'Zend/Uri/Http.php';
+require_once 'Zend/Uri/Mailto.php';
+
+abstract class Zend_Uri
+{
+
+  /**
+   * Return a string representation of this URI.
+   *
+   * @see     getUri()
+   * @return  string
+   */
+  public function __toString()
+  {
+      return $this->getUri();
+  }
+
+  static public function factory($uri = 'http')
+  {
+      $uri = explode(':', $uri, 2);
+      $scheme = strtolower($uri[0]);
+      $schemeSpecific = isset($uri[1]) ? $uri[1] : '';
+      $desc = 'Multi
+line description';
+
+      // Security check: $scheme is used to load a class file,
+      // so only alphanumerics are allowed.
+      if (!ctype_alnum($scheme)) {
+          throw new Zend_Uri_Exception('Illegal scheme');
+      }
+  }
+}
+
+__halt_compiler () ; datahere
+datahere
+datahere */
+datahere
+
+
+ +
Scala + +
object abstractTypes extends Application {
+  abstract class SeqBuffer {
+    type T; val element: Seq[T]; def length = element.length
+  }
+}
+
+/** Turn command line arguments to uppercase */
+object Main {
+  def main(args: Array[String]) {
+    val res = for (a <- args) yield a.toUpperCase
+    println("Arguments: " + res.toString)
+  }
+}
+
+/** Maps are easy to use in Scala. */
+object Maps {
+  val colors = Map("red" -> 0xFF0000,
+                   "turquoise" -> 0x00FFFF,
+                   "black" -> 0x000000,
+                   "orange" -> 0xFF8040,
+                   "brown" -> 0x804000)
+  def main(args: Array[String]) {
+    for (name <- args) println(
+      colors.get(name) match {
+        case Some(code) =>
+          name + " has code: " + code
+        case None =>
+          "Unknown color: " + name
+      }
+    )
+  }
+}
+
+ +
Go + +
package main
+
+import (
+    "fmt"
+    "rand"
+    "os"
+)
+
+const (
+    Sunday = iota
+    Partyday
+    numberOfDays  // this constant is not exported
+)
+
+type Foo interface {
+    FooFunc(int, float32) (complex128, []int)
+}
+
+// simple comment
+type Bar struct {
+    os.File /* multi
+    line
+    comment */
+
+    PublicData chan int
+}
+
+func main() {
+    ch := make(chan int)
+    ch <- 1
+    x, ok := <- ch
+    ok = true
+    x = nil
+    float_var := 1.0e10
+    defer fmt.Println('\'')
+    defer fmt.Println(`exitting now\`)
+    var fv1 float64 = 0.75
+    go println(len("hello world!"))
+    return
+}
+
+
+ +
XML + +
<?xml version="1.0"?>
+<response value="ok" xml:lang="en">
+  <text>Ok</text>
+  <comment html_allowed="true"/>
+  <ns1:description><![CDATA[
+  CDATA is <not> magical.
+  ]]></ns1:description>
+  <a></a> <a/>
+</response>
+
+ +
HTML (with inline css and javascript) + +
<!DOCTYPE html5>
+<head>
+  <title>Title</title>
+
+  <style>
+    body {
+      width: 500px;
+    }
+  </style>
+
+  <script type="application/javascript">
+    function someFunction() {
+      return true;
+    }
+  </script>
+
+<body>
+  <p class="something" id='12'>Something</p>
+  <p class=something>Something</p>
+  <!-- comment -->
+  <p class>Something</p>
+  <p class="something" title="p">Something</p>
+</body>
+
+ +
Markdown + +
+# hello world
+
+you can write text [with links](http://example.com).
+
+* one _thing_ has *em*phasis
+* two __things__ are **bold**
+
+---
+
+hello world
+===========
+
+<this_is inline="xml"></this_is>
+
+> markdown is so cool
+
+    so are code segments
+
+1. one thing (yeah!)
+2. two thing `i can write code`, and `more` wipee!
+
+ +
Django templates + +
{% if articles|length %}
+{% for article in articles %}
+
+{# Striped table #}
+<tr class="{% cycle odd,even %}">
+  <td>{{ article|default:"Hi... "|escape }}</td>
+  <td {% if article.today %}class="today"{% endif %}>{{ article.date|date:"d.m.Y" }}</td>
+</tr>
+
+{% endfor %}
+{% endif %}
+
+{% comment %}
+Comments may be long and
+multiline.
+{% endcomment %}
+
+ +
CSS + +
body,
+html {
+  font: Tahoma, Arial, san-serif;
+  background: url('hatch.png');
+}
+
+@import url('print.css');
+
+@media screen and (-webkit-min-device-pixel-ratio: 0) {
+  body:first-of-type pre::after {
+    content: 'highlight: ' attr(class);
+  }
+}
+
+@page:right {
+ margin: 1cm 2cm 1.3cm 4cm;
+}
+
+@font-face {
+	font-family: Chunkfive;
+	src: url('Chunkfive.otf');
+}
+
+#content {
+  width: /* wide enough */ 100% /* 400px */;
+  height: 100%
+}
+
+p[lang=ru] {
+  color: #F0F0F0; background: white !important;
+}
+
+ +
JavaScript + +
function $initHighlight(block) {
+  if (block.className.search(/\bno\-highlight\b/) != -1)
+    return false;
+  try {
+    blockText(block);
+  } catch (e) {
+    if (e == 'Complex markup')
+      return;
+  }//try
+  var classes = block.className.split(/\s+/);
+  for (var i = 0 / 2; i < classes.length; i++) { // "0 / 2" should not be parsed as regexp start
+    if (LANGUAGES[classes[i]]) {
+      highlightLanguage(block, classes[i]);
+      return;
+    }//if
+  }//for
+  highlightAuto(block);
+}//initHighlight
+ +
CoffeeScript + +
grade = (student) ->
+  if student.excellentWork
+    "A+"
+  else if student.okayStuff
+    if student.triedHard then "B" else "B-"
+  else
+    "C"
+
+eldest = if 24 > 21 then "Liz" else "Ike"
+
+square = (x) -> x * x
+
+two = -> 2
+
+math =
+  root:   Math.sqrt
+  square: square
+  cube:   (x) -> x * square x
+
+race = (winner, runners...) ->
+  print winner, runners
+
+hi = `function() {
+  return [document.title, "Hello JavaScript"].join(": ");
+}`
+
+substr = "JavaScript numbers test #{ 010 / 0xf }"
+
+heredoc = """
+CoffeeScript numbers test #{ 010 / 0b10 }
+"""
+
+###
+CoffeeScript Compiler v1.2.0
+Released under the MIT License
+###
+
+OPERATOR = /// ^ (
+?: [-=]>             # function
+ | [-+*/%<>&|^!?=]=  # compound assign / compare
+ | >>>=?             # zero-fill right shift
+ | ([-+:])\1         # doubles
+ | ([&|<>])\2=?      # logic / shift
+ | \?\.              # soak access
+ | \.{2,3}           # range or splat
+) ///
+ +
ActionScript + +
package org.example.dummy {
+    import org.dummy.*;
+
+    /*define package inline interface*/
+    public interface IFooBarzable {
+        public function foo(... pairs):Array;
+    }
+
+    public class FooBar implements IFooBarzable {
+        static private var cnt:uint = 0;
+
+        private var bar:String;
+
+        //constructor
+        public function TestBar(bar:String):void {
+            bar = bar;
+
+            ++cnt;
+        }
+
+        public function foo(... pairs):Array {
+            pairs.push(bar);
+
+            return pairs;
+        }
+
+        protected function includeTestFile():void {
+            include "Test.as";
+        }
+    }
+}
+ +
VBScript + +
' creating configuration storage and initializing with default values
+Set cfg = CreateObject("Scripting.Dictionary")
+
+' reading ini file
+for i = 0 to ubound(ini_strings)
+    s = trim(ini_strings(i))
+
+    ' skipping empty strings and comments
+    if mid(s, 1, 1) <> "#" and len(s) > 0 then
+      ' obtaining key and value
+      parts = split(s, "=", -1, 1)
+
+      if ubound(parts)+1 = 2 then
+        parts(0) = trim(parts(0))
+        parts(1) = trim(parts(1))
+
+        ' reading configuration and filenames
+        select case lcase(parts(0))
+          case "uncompressed""_postfix" cfg.item("uncompressed""_postfix") = parts(1)
+          case "f"
+                    options = split(parts(1), "|", -1, 1)
+                    if ubound(options)+1 = 2 then
+                      ' 0: filename,  1: options
+                      ff.add trim(options(0)), trim(options(1))
+                    end if
+        end select
+      end if
+    end if
+next
+ +
Lua + +
--[[
+Simple signal/slot implementation
+]]
+local signal_mt = {
+    __index = {
+        register = table.insert
+    }
+}
+function signal_mt.__index:emit(... --[[ Comment in params ]])
+    for _, slot in ipairs(self) do
+        slot(self, ...)
+    end
+end
+local function create_signal()
+    return setmetatable({}, signal_mt)
+end
+
+-- Signal test
+local signal = create_signal()
+signal:register(function (signal, ...)
+    print(...)
+end)
+signal:emit('Answer to Life, the Universe, and Everything:', 42)
+
+--[==[ [=[ [[
+Nested ]]
+multi-line ]=]
+comment ]==]
+[==[ Nested
+[=[ multi-line
+[[ string
+]] ]=] ]==]
+
+ +
Delphi + +
TList=Class(TObject)
+Private
+  Some: String;
+Public
+  Procedure Inside; // Suxx
+End;{TList}
+
+Procedure CopyFile(InFileName,var OutFileName:String);
+Const
+  BufSize=4096; (* Huh? *)
+Var
+  InFile,OutFile:TStream;
+  Buffer:Array[1..BufSize] Of Byte;
+  ReadBufSize:Integer;
+Begin
+  InFile:=Nil;
+  OutFile:=Nil;
+  Try
+    InFile:=TFileStream.Create(InFileName,fmOpenRead);
+    OutFile:=TFileStream.Create(OutFileName,fmCreate);
+    Repeat
+      ReadBufSize:=InFile.Read(Buffer,BufSize);
+      OutFile.Write(Buffer,ReadBufSize);
+    Until ReadBufSize<>BufSize;
+    Log('File '''+InFileName+''' copied'#13#10);
+  Finally
+    InFile.Free;
+    OutFile.Free;
+  End;{Try}
+End;{CopyFile}
+
+ +
Java + +
package l2f.gameserver.model;
+
+import java.util.ArrayList;
+
+/**
+ * Mother class of all character objects of the world (PC, NPC...)<BR><BR>
+ *
+ */
+public abstract class L2Character extends L2Object
+{
+  protected static final Logger _log = Logger.getLogger(L2Character.class.getName());
+
+  public static final Short ABNORMAL_EFFECT_BLEEDING = 0x0001; // not sure
+  public static final Short ABNORMAL_EFFECT_POISON = 0x0002;
+
+  public void detachAI() {
+    _ai = null;
+    //jbf = null;
+    if (1 > 5) {
+      return;
+    }
+  }
+
+  public void moveTo(int x, int y, int z) {
+    moveTo(x, y, z, 0);
+  }
+
+  /** Task of AI notification */
+  @SuppressWarnings( { "nls", "unqualified-field-access", "boxing" })
+  public class NotifyAITask implements Runnable {
+    private final CtrlEvent _evt;
+
+    public void run() {
+      try {
+        getAI().notifyEvent(_evt, null, null);
+      } catch (Throwable t) {
+        _log.warning("Exception " + t);
+        t.printStackTrace();
+      }
+    }
+  }
+
+}
+
+ +
C++ + +
#include <iostream>
+
+int main(int argc, char *argv[]) {
+
+  /* An annoying "Hello World" example */
+  for (auto i = 0; i < 0xFFFF; i++)
+    cout << "Hello, World!" << endl;
+
+  char c = '\n';
+  unordered_map <string, vector<string> > m;
+  m["key"] = "\\\\"; // this is an error
+
+  return -2e3 + 12l;
+}
+
+ +
Objective C + +
+
+#import <UIKit/UIKit.h>
+#import "Dependency.h"
+
+@protocol WorldDataSource
+@optional
+- (NSString*)worldName;
+@required
+- (BOOL)allowsToLive;
+@end
+
+@interface Test : NSObject <HelloDelegate, WorldDataSource> {
+	NSString *_greeting;
+}
+
+@property (nonatomic, readonly) NSString *greeting;
+- (IBAction) show;
+@end
+
+@implementation Test
+
+@synthesize test=_test;
+
++ (id) test {
+	return [self testWithGreeting:@"Hello, world!\nFoo bar!"];
+}
+
++ (id) testWithGreeting:(NSString*)greeting {
+	return [[[self alloc] initWithGreeting:greeting] autorelease];
+}
+
+- (id) initWithGreeting:(NSString*)greeting {
+	if ( (self = [super init]) ) {
+		_greeting = [greeting retain];
+	}
+	return self;
+}
+
+- (void) dealloc {
+	[_greeting release];
+	[super dealloc];
+}
+
+@end
+
+
+ +
Vala + +
using DBus;
+
+namespace Test {
+  class Foo : Object {
+    public signal void some_event ();   // definition of the signal
+    public void method () {
+      some_event ();                    // emitting the signal (callbacks get invoked)
+    }
+  }
+}
+
+/* defining a class */
+class Track : GLib.Object {              /* subclassing 'GLib.Object' */
+	public double mass;                  /* a public field */
+	public double name { get; set; }     /* a public property */
+	private bool terminated = false;     /* a private field */
+	public void terminate() {            /* a public method */
+	  terminated = true;
+	}
+}
+
+const ALL_UPPER_CASE = "you should follow this convention";
+
+var t = new Track();      // same as: Track t = new Track();
+var s = "hello";          // same as: string s = "hello";
+var l = new List<int>();       // same as: List<int> l = new List<int>();
+var i = 10;               // same as: int i = 10;
+
+
+#if (ololo)
+Regex regex = /foo/;
+#endif
+
+/*
+ * Entry point can be outside class
+ */
+void main () {
+  var long_string = """
+    Example of "verbatim string".
+    Same as in @"string" in C#
+  """
+  var foo = new Foo ();
+  foo.some_event.connect (callback_a);      // connecting the callback functions
+  foo.some_event.connect (callback_b);
+  foo.method ();
+}
+
+ +
C# + +
using System;
+
+#pragma warning disable 414, 3021
+
+public class Program
+{
+    /// <summary>The entry point to the program.</summary>
+    /// <remarks>
+    /// Using the Visual Studio style, the tags in this comment
+    /// should be grey, but this text should be green.
+    /// This comment should be green on the inside:
+    /// <!-- I'm green! -->
+    /// </remarks>
+    public static int Main(string[] args)
+    {
+        Console.WriteLine("Hello, World!");
+        string s = @"This
+""string""
+spans
+multiple
+lines!";
+        return 0;
+    }
+}
+
+ +
RenderMan RSL + +
#define TEST_DEFINE 3.14
+/*	plastic surface shader
+ *
+ * 	Pixie is:
+ * 	(c) Copyright 1999-2003 Okan Arikan. All rights reserved.
+ */
+
+surface plastic (float Ka = 1, Kd = 0.5, Ks = 0.5, roughness = 0.1;
+                 color specularcolor = 1;) {
+  normal Nf = faceforward (normalize(N),I);
+  Ci = Cs * (Ka*ambient() + Kd*diffuse(Nf)) + specularcolor * Ks *
+       specular(Nf,-normalize(I),roughness);
+  Oi = Os;
+  Ci *= Oi;
+}
+
+ +
RenderMan RIB + +
FrameBegin 0
+Display "Scene" "framebuffer" "rgb"
+Option "searchpath" "shader" "+&:/home/kew"
+Option "trace" "int maxdepth" [4]
+Attribute "visibility" "trace" [1]
+Attribute "irradiance" "maxerror" [0.1]
+Attribute "visibility" "transmission" "opaque"
+Format 640 480 1.0
+ShadingRate 2
+PixelFilter "catmull-rom" 1 1
+PixelSamples 4 4
+Projection "perspective" "fov" 49.5502811377
+Scale 1 1 -1
+
+WorldBegin
+
+ReadArchive "Lamp.002_Light/instance.rib"
+Surface "plastic"
+ReadArchive "Cube.004_Mesh/instance.rib"
+# ReadArchive "Sphere.010_Mesh/instance.rib"
+# ReadArchive "Sphere.009_Mesh/instance.rib"
+ReadArchive "Sphere.006_Mesh/instance.rib"
+
+WorldEnd
+FrameEnd
+
+ +
MEL (Maya Embedded Language) + +
proc string[] getSelectedLights()
+
+{
+  string $selectedLights[];
+
+  string $select[] = `ls -sl -dag -leaf`;
+
+  for ( $shape in $select )
+  {
+    // Determine if this is a light.
+    //
+    string $class[] = getClassification( `nodeType $shape` );
+
+
+    if ( ( `size $class` ) > 0 && ( "light" == $class[0] ) )
+    {
+      $selectedLights[ `size $selectedLights` ] = $shape;
+    }
+  }
+
+  // Result is an array of all lights included in
+
+  // current selection list.
+  return $selectedLights;
+}
+
+ +
SQL + +
BEGIN;
+CREATE TABLE "cicero_topic" (
+    "id" serial NOT NULL PRIMARY KEY,
+    "forum_id" integer NOT NULL,
+    "subject" varchar(255) NOT NULL,
+    "created" timestamp with time zone NOT NULL
+);
+ALTER TABLE "cicero_topic"
+ADD CONSTRAINT forum_id_refs_id_4be56999
+FOREIGN KEY ("forum_id")
+REFERENCES "cicero_forum" ("id")
+DEFERRABLE INITIALLY DEFERRED;
+
+-- Initials
+insert into "cicero_forum"
+  ("slug", "name", "group", "ordering")
+values
+  ('test', 'Forum for te''sting', 'Test', 0);
+
+-- Test
+select count(*) from cicero_forum;
+
+COMMIT;
+
+ +
SmallTalk + +
Object>>method: num
+    "comment 123"
+    | var1 var2 |
+    (1 to: num) do: [:i | |var| ^i].
+    Klass with: var1.
+    Klass new.
+    arr := #('123' 123.345 #hello Transcript var $@).
+    arr := #().
+    var2 = arr at: 3.
+    ^ self abc
+
+heapExample
+    "HeapTest new heapExample"
+    "Multiline
+    decription"
+    | n rnd array time sorted |
+    n := 5000.
+    "# of elements to sort"
+    rnd := Random new.
+    array := (1 to: n)
+                collect: [:i | rnd next].
+    "First, the heap version"
+    time := Time
+                millisecondsToRun: [sorted := Heap withAll: array.
+    1
+        to: n
+        do: [:i |
+            sorted removeFirst.
+            sorted add: rnd next]].
+    Transcript cr; show: 'Time for Heap: ' , time printString , ' msecs'.
+    "The quicksort version"
+    time := Time
+                millisecondsToRun: [sorted := SortedCollection withAll: array.
+    1
+        to: n
+        do: [:i |
+            sorted removeFirst.
+            sorted add: rnd next]].
+    Transcript cr; show: 'Time for SortedCollection: ' , time printString , ' msecs'
+
+ +
Lisp + +
(defun prompt-for-cd ()
+   "Prompts
+    for CD"
+   (prompt-read "Title" 1.53 1 2/4 1.7 1.7e0 2.9E-4 +42 -7 #b001 #b001/100 #o777 #O777 #xabc55 #c(0 -5.6))
+   (prompt-read "Artist" &rest)
+   (or (parse-integer (prompt-read "Rating") :junk-allowed t) 0)
+  (if x (format t "yes") (format t "no" nil) ;and here comment
+  )
+  ;; second line comment
+  '(+ 1 2)
+  (defvar *lines*)                ; list of all lines
+  (position-if-not #'sys::whitespacep line :start beg))
+  (quote (privet 1 2 3))
+  '(hello world)
+  (* 5 7)
+  (1 2 34 5)
+  (:use "aaaa")
+  (let ((x 10) (y 20))
+    (print (+ x y))
+  )
+ +
Ini file + +
;Settings relating to the location and loading of the database
+[Database]
+ProfileDir=.
+ShowProfileMgr=smart
+Profile1_Name[] = "\|/_-=MegaDestoyer=-_\|/"
+DefaultProfile=True
+AutoCreate = no
+
+[AutoExec]
+Use="prompt"
+Glob=autoexec_*.ini
+AskAboutIgnoredPlugins=0
+
+ +
Apache + +
# rewrite`s rules for wordpress pretty url
+LoadModule rewrite_module  modules/mod_rewrite.so
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteCond %{REQUEST_FILENAME} !-d
+RewriteRule . index.php [NC,L]
+
+ExpiresActive On
+ExpiresByType application/x-javascript  "access plus 1 days"
+
+<Location /maps/>
+  RewriteMap map txt:map.txt
+  RewriteMap lower int:tolower
+  RewriteCond %{REQUEST_URI} ^/([^/.]+)\.html$ [NC]
+  RewriteCond ${map:${lower:%1}|NOT_FOUND} !NOT_FOUND
+  RewriteRule .? /index.php?q=${map:${lower:%1}} [NC,L]
+</Location>
+
+ +
nginx + +
user  www www;
+worker_processes  2;
+pid /var/run/nginx.pid;
+error_log  /var/log/nginx.error_log  debug | info | notice | warn | error | crit;
+
+events {
+    connections   2000;
+    use kqueue | rtsig | epoll | /dev/poll | select | poll;
+}
+
+http {
+    log_format main      '$remote_addr - $remote_user [$time_local] '
+                         '"$request" $status $bytes_sent '
+                         '"$http_referer" "$http_user_agent" '
+                         '"$gzip_ratio"';
+
+    send_timeout 3m;
+    client_header_buffer_size 1k;
+
+    gzip on;
+    gzip_min_length 1100;
+
+    #lingering_time 30;
+
+    server {
+        listen        one.example.com;
+        server_name   one.example.com  www.one.example.com;
+        access_log   /var/log/nginx.access_log  main;
+
+        location / {
+            proxy_pass         http://127.0.0.1/;
+            proxy_redirect     off;
+            proxy_set_header   Host             $host;
+            proxy_set_header   X-Real-IP        $remote_addr;
+            charset            koi8-r;
+        }
+
+        location ~* \.(jpg|jpeg|gif)$ {
+            root         /spool/www;
+        }
+    }
+}
+
+ +
Diff + +
Index: languages/ini.js
+===================================================================
+--- languages/ini.js    (revision 199)
++++ languages/ini.js    (revision 200)
+@@ -1,8 +1,7 @@
+ hljs.LANGUAGES.ini =
+ {
+   case_insensitive: true,
+-  defaultMode:
+-  {
++  defaultMode: {
+     contains: ['comment', 'title', 'setting'],
+     illegal: '[^\\s]'
+   },
+
+*** /path/to/original timestamp
+--- /path/to/new      timestamp
+***************
+*** 1,3 ****
+--- 1,9 ----
++ This is an important
++ notice! It should
++ therefore be located at
++ the beginning of this
++ document!
+
+! compress the size of the
+! changes.
+
+  It is important to spell
+
+ +
DOS batch files + +
cd \
+copy a b
+ping 192.168.0.1
+@rem ping 192.168.0.1
+net stop sharedaccess
+del %tmp% /f /s /q
+del %temp% /f /s /q
+ipconfig /flushdns
+taskkill /F /IM JAVA.EXE /T
+
+cd Photoshop/Adobe Photoshop CS3/AMT/
+if exist application.sif (
+    ren application.sif _application.sif
+) else (
+    ren _application.sif application.sif
+)
+
+taskkill /F /IM proquota.exe /T
+
+sfc /SCANNOW
+
+set path = test
+
+xcopy %1\*.* %2
+
+ +
Bash + +
#!/bin/bash
+
+###### BEGIN CONFIG
+ACCEPTED_HOSTS="/root/.hag_accepted.conf"
+BE_VERBOSE=false
+###### END CONFIG
+
+if [ "$UID" -ne 0 ]
+then
+ echo "Superuser rights is required"
+ echo 'Printing the # sign'
+ exit 2
+fi
+
+genApacheConf(){
+ if [[ "$2" = "www" ]]
+ then
+  full_domain=$1
+ else
+  full_domain=$2.$1
+ fi
+ host_root="${APACHE_HOME_DIR}$1/$2"
+ echo -e "# Host $1/$2 :"
+}
+
+ +
CMake + +
project(test)
+cmake_minimum_required(VERSION 2.6)
+
+# IF LINUX
+if (${CMAKE_SYSTEM_NAME} MATCHES Linux)
+    message("\nOS:\t\tLinux")
+endif()
+
+# IF WINDOWS
+if (${CMAKE_SYSTEM_NAME} MATCHES Windows)
+    message("\nOS:\t\tWindows")
+endif()
+
+set(test test0.cpp test1.cpp test2.cpp)
+
+include_directories(./)
+
+set(EXECUTABLE_OUTPUT_PATH ../bin)
+
+add_subdirectory(src)
+
+add_executable(test WIN32 ${test})
+
+target_link_libraries(test msimg32)
+
+ +
Axapta + +
class ExchRateLoadBatch extends RunBaseBatch {
+  ExchRateLoad rbc;
+  container currencies;
+  boolean actual;
+  boolean overwrite;
+  date beg;
+  date end;
+
+  #define.CurrentVersion(5)
+
+  #localmacro.CurrentList
+    currencies,
+    actual,
+    beg,
+    end
+  #endmacro
+}
+
+public boolean unpack(container packedClass) {
+  container       base;
+  boolean         ret;
+  Integer         version    = runbase::getVersion(packedClass);
+
+  switch (version) {
+    case #CurrentVersion:
+      [version, #CurrentList] = packedClass;
+      return true;
+    default:
+      return false;
+  }
+  return ret;
+}
+
+ +
1С + +

+#Если Клиент Тогда
+Перем СимвольныйКодКаталога = "ля-ля-ля"; //комментарий
+Функция Сообщить(Знач ТекстСообщения, ТекстСообщения2) Экспорт //комментарий к функции
+  x=ТекстСообщения+ТекстСообщения2+"
+  |строка1
+  |строка2
+  |строка3";
+КонецФункции
+#КонецЕсли
+
+// Процедура ПриНачалеРаботыСистемы
+//
+Процедура ПриНачалеРаботыСистемы()
+  Обработки.Помощник.ПолучитьФорму("Форма").Открыть();
+  d = '21.01.2008'
+КонецПроцедуры
+
+ +
AVR Assembler + +
;* Title:       Block Copy Routines
+;* Version:     1.1
+
+.include "8515def.inc"
+
+    rjmp    RESET   ;reset handle
+
+.def    flashsize=r16       ;size of block to be copied
+
+flash2ram:
+    lpm         ;get constant
+    st  Y+,r0       ;store in SRAM and increment Y-pointer
+    adiw    ZL,1        ;increment Z-pointer
+    dec flashsize
+    brne    flash2ram   ;if not end of table, loop more
+    ret
+
+.def    ramtemp =r1     ;temporary storage register
+.def    ramsize =r16        ;size of block to be copied
+
+ +
VHDL + +
------------------------------------
+-- RS Trigger with Assynch. Reset --
+------------------------------------
+
+library IEEE;
+use IEEE.STD_LOGIC_1164.all;
+
+entity RS_AR is
+	generic (T: Time := 0ns);
+
+	port(
+		 -- Default RS Trigger
+		 R  : in  STD_LOGIC;
+		 S  : in  STD_LOGIC;
+		 Q  : out STD_LOGIC;
+		 nQ : out STD_LOGIC;
+
+		 -- Special Input Signals
+		 AR : in  STD_LOGIC; -- assynch. reset
+		 C  : in  STD_LOGIC  -- synch. signal
+	     );
+end RS_AR;
+
+
+architecture RS_AR of RS_AR is
+	signal QT: std_logic; -- Q(t)
+begin
+
+	process(C, AR) is
+		subtype RS is std_logic_vector ( 1 downto 0 );
+	begin
+		if AR='0' then
+			QT <= '0';
+		else
+			if rising_edge(C) then
+
+				if not (R'stable(T) and S'stable(T)) then
+				QT <= 'X';
+				else
+
+				case RS'(R&S) is
+					when "01" => QT <= '1';
+					when "10" => QT <= '0';
+					when "11" => QT <= 'X';
+					when others => null;
+				end case;
+
+				end if;
+			end if;
+		end if;
+	end process;
+
+	Q  <= QT;
+	nQ <= not QT;
+
+end RS_AR;
+
+ +
Parser 3 + +
@CLASS
+base
+
+@USE
+module.p
+
+@BASE
+class
+
+# Comment for code
+@create[aParam1;aParam2][local1;local2]
+  ^connect[mysql://host/database?ClientCharset=windows-1251]
+  ^for[i](1;10){
+    <p class="paragraph">^eval($i+10)</p>
+    ^connect[mysql://host/database]{
+      $tab[^table::sql{select * from `table` where a='1'}]
+      $var_Name[some${value}]
+    }
+  }
+
+  ^rem{
+    Multiline comment with code: $var
+    ^while(true){
+      ^for[i](1;10){
+        ^sleep[]
+      }
+    }
+  }
+  ^taint[^#0A]
+
+@GET_base[]
+## Comment for code
+  # Isn't comment
+  $result[$.hash_item1[one] $.hash_item2[two]]
+
+ +
TeX + +
+\documentclass{article}
+\usepackage[koi8-r]{inputenc}
+\hoffset=0pt
+\voffset=.3em
+\tolerance=400
+\newcommand{\eTiX}{\TeX}
+\begin{document}
+\section*{Highlight.js}
+\begin{table}[c|c]
+$\frac 12\, + \, \frac 1{x^3}\text{Hello \! world}$ & \textbf{Goodbye\~ world} \\\eTiX $ \pi=400 $
+\end{table}
+Ch\'erie, \c{c}a ne me pla\^\i t pas! % comment \b
+G\"otterd\"ammerung~45\%=34.
+$$
+    \int\limits_{0}^{\pi}\frac{4}{x-7}=3
+$$
+\end{document}
+
+ +
Haskell + +
+module Shapes
+( Point(..)  ,
+  Shape(..)  ,
+  surface    ,
+  baseCircle ,
+  baseRect
+) where
+
+-- Single line comment
+{-
+multi
+line
+comment
+-}
+data Point = Point Float Float deriving (Show)
+data Shape = Circle Point Float | Rectangle Point Point deriving (Show)
+
+surface :: Shape -> Float
+surface (Circle _ r) = pi * r^2
+surface (Rectangle (Point x1 y1) (Point x2 y2)) = (abs $ x2 - x1) * (abs $ y2 - y1)
+
+baseCircle :: Float -> Shape
+baseCircle r = Circle(Point 0 0) r
+
+baseRect :: Float -> Float -> Shape
+baseRect w h = Rectangle (Point 0 0) (Point w h)
+
+ +
Erlang + +
-module(ssh_cli).
+
+-behaviour(ssh_channel).
+
+-include("ssh.hrl").
+%% backwards compatibility
+-export([listen/1, listen/2, listen/3, listen/4, stop/1]).
+
+%% state
+-record(state, {
+	  cm,
+	  channel
+	 }).
+
+test(Foo)->Foo.
+
+init([Shell, Exec]) ->
+    {ok, #state{shell = Shell, exec = Exec}};
+init([Shell]) ->
+    false = not true,
+    io:format("Hello, \"~p!~n", [atom_to_list('World')]),
+    {ok, #state{shell = Shell}}.
+
+concat([Single]) -> Single;
+concat(RList) ->
+    EpsilonFree = lists:filter(
+        fun (Element) ->
+            case Element of
+                epsilon -> false;
+                _ -> true
+            end
+        end,
+        RList),
+    case EpsilonFree of
+        [Single] -> Single;
+        Other -> {concat, Other}
+    end.
+
+union_dot_union({union, _}=U1, {union, _}=U2) ->
+    union(lists:flatten(
+        lists:map(
+            fun (X1) ->
+                lists:map(
+                    fun (X2) ->
+                        concat([X1, X2])
+                    end,
+                    union_to_list(U2)
+                )
+            end,
+            union_to_list(U1)
+        ))).
+
+ +
Erlang REPL + +
1> Str = "abcd".
+"abcd"
+2> L = test:length(Str).
+4
+3> Descriptor = {L, list_to_atom(Str)}.
+{4,abcd}
+4> L.
+4
+5> b().
+Descriptor = {4,abcd}
+L = 4
+Str = "abcd"
+ok
+6> f(L).
+ok
+7> b().
+Descriptor = {4,abcd}
+Str = "abcd"
+ok
+8> {L, _} = Descriptor.
+{4,abcd}
+9> L.
+4
+10> 2#101.
+5
+11> 1.85e+3.
+1850
+
+ +
Rust + +
+use std;
+
+import std::io;
+export fac, test1;
+
+123;                               // type int
+123u;                              // type uint
+123_u;                             // type uint
+0xff00;                            // type int
+0xff_u8;                           // type u8
+0b1111_1111_1001_0000_i32;         // type i32
+123.0;                             // type float
+0.1;                               // type float
+3f;                                // type float
+0.1f32;                            // type f32
+12E+99_f64;                        // type f64
+
+/* Factorial */
+fn fac(n: int) -> int {
+    let s: str = "This is
+a multi-line string.
+
+It ends with an unescaped '\"'.";
+    let c: char = 'Ф';
+
+    let result = 1, i = 1;
+    while i <= n { // No parens around the condition
+        result *= i;
+        i += 1;
+    }
+    ret result;
+}
+
+pure fn pure_length<T>(ls: list<T>) -> uint { /* ... */ }
+
+type t = map::hashtbl<int,str>;
+let x = id::<int>(10);
+
+// Define some modules.
+#[path = "foo.rs"]
+mod foo;
+
+iface seq<T> {
+    fn len() -> uint;
+}
+
+impl <T> of seq<T> for [T] {
+    fn len() -> uint { vec::len(self) }
+    fn iter(b: fn(T)) {
+        for elt in self { b(elt); }
+    }
+}
+
+enum list<T> {
+    nil;
+    cons(T, @list<T>);
+}
+
+let a: list<int> = cons(7, @cons(13, @nil));
+
+ +
Matlab + +
n = 20; % number of points
+points = [random('unid', 100, n, 1), random('unid', 100, n, 1)];
+len = zeros(1, n - 1);
+points = sortrows(points);
+%% Initial set of points
+plot(points(:,1),points(:,2));
+for i = 1: n-1
+    len(i) = points(i + 1, 1) - points(i, 1);
+end
+while(max(len) > 2 * min(len))
+    [d, i] = max(len);
+    k = on_margin(points, i, d, -1);
+    m = on_margin(points, i + 1, d, 1);
+    xm = 0; ym = 0;
+%% New point
+    if(i == 1 || i + 1 == n)
+        xm = mean(points([i,i+1],1))
+        ym = mean(points([i,i+1],2))
+    else
+        [xm, ym] = dlg1(points([k, i, i + 1, m], 1), ...
+            points([k, i, i + 1, m], 2))
+    end
+
+    points = [ points(1:i, :); [xm, ym]; points(i + 1:end, :)];
+end
+
+function [net] = get_fit_network(inputs, targets)
+    % Create Network
+    numHiddenNeurons = 20;  % Adjust as desired
+    net = newfit(inputs,targets,numHiddenNeurons);
+    net.trainParam.goal = 0.01;
+    net.trainParam.epochs = 1000;
+    % Train and Apply Network
+    [net,tr] = train(net,inputs,targets);
+end
+
+ +
+ + + + +

Special tests

+ + + + + + + + + + +
Explicit Python highlighting + +
for x in [1, 2, 3]:
+  count(x)
+
+ +
Language set on <pre> + +
for x in [1, 2, 3]:
+  count(x)
+
+ +
HTML5-style language class (language-python) + +
for x in [1, 2, 3]:
+  count(x)
+
+ +
Replacing TAB with 4 spaces + +
for x in [1, 2, 3]:
+	count(x)
+
+ +
Custom markup + +
<div id="contents">
+  <p>Hello, World!Goodbye, cruel world!
+</div>
+
+ +
Custom markup + TAB replacement + +
for x in [1, 2, 3]:
+	count(x)
+	if x == 3:
+		count(x + 1)
+
+ +
Non-pre container + +
for x in [1, 2, 3]:
+  count(x)
+
+ + +
Disabled highlighting + +
<div id="contents">
+  <p>Hello, World!
+</div>
+
+ +
diff --git a/output/Scratch/sitemap.xml b/output/Scratch/sitemap.xml index 48d9df4d3..8bfdf36b1 100644 --- a/output/Scratch/sitemap.xml +++ b/output/Scratch/sitemap.xml @@ -1,267 +1,59 @@ - http://yannesposito.com/Scratch/fr/blog/2009-10-How-to-preload-your-site-with-style/ + http://yannesposito.com/Scratch/fr/blog/2010-05-19-How-to-cut-HTML-and-repair-it/ 2012-06-05 - http://yannesposito.com/Scratch/fr/blog/09_Why_I_didn-t_keep_whosamung-us/ + http://yannesposito.com/Scratch/fr/blog/2010-02-23-When-regexp-is-not-the-best-solution/ 2012-06-05 - http://yannesposito.com/Scratch/fr/blog/07_Screensaver_compilation_option_for_Snow_Leopard/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/softwares/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/rss/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/Password-Management/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2009-10-launch-daemon-from-command-line/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/Yesod-excellent-ideas/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/Yesod-tutorial-for-newbies/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2011-01-03-Why-I-sadly-won-t-use-coffeescript/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2010-03-23-Encapsulate-git/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2010-01-12-antialias-font-in-Firefox-under-Ubuntu/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/latest/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2010-09-02-Use-git-to-calculate-trusted-mtimes/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2009-11-12-Git-for-n00b/comprendre/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/about/cv/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/Haskell-Mandelbrot/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2010-09-02-Use-git-to-calculate-trusted-mtimes/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2009-10-30-How-to-handle-evil-IE/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/09_Why_I_didn-t_keep_whosamung-us/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2009-11-12-Git-for-n00b/comprendre/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/Typography-and-the-Web/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2009-10-30-How-to-handle-evil-IE/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2010-05-17-at-least-this-blog-revive/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2010-10-26-LaTeX-like-macro-and-markdown/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/Yesod-tutorial-for-newbies/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/10_Synchronize_Custom_WebSite_with_mobileMe/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/01_nanoc/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2010-02-18-split-a-file-by-keyword/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2010-05-17-at-least-this-blog-revive/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/03_losthighway/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2010-05-19-How-to-cut-HTML-and-repair-it/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/06_How_I_use_git/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2010-01-04-Change-default-shell-on-Mac-OS-X/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/Higher-order-function-in-zsh/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/softwares/ypassword/iphoneweb/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2010-06-14-multi-language-choices/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2009-11-12-Git-for-n00b/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2009-10-28-custom-website-synchronisation-with-mobileme--2-/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/about/technical_details/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2009-10-How-to-preload-your-site-with-style/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/03_losthighway/03_losthighway_2/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/11_Load_Disqus_Asynchronously/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2011-01-03-Happy-New-Year/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2010-02-15-All-but-something-regexp/ + http://yannesposito.com/Scratch/fr/about/old/ 2012-06-05 http://yannesposito.com/Scratch/en/blog/2009-12-06-iphone-call-filter/ 2012-06-05 - - http://yannesposito.com/Scratch/fr/blog/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/SVG-and-m4-fractals/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2010-07-09-Indecidabilities/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2010-10-06-New-Blog-Design-Constraints/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/03_losthighway/03_losthighway_3/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2010-03-22-Git-Tips/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/01_nanoc/ - 2012-06-05 - http://yannesposito.com/Scratch/en/blog/2009-10-launch-daemon-from-command-line/ 2012-06-05 - http://yannesposito.com/Scratch/en/blog/2009-09-Disqus-versus-Intense-Debate--Why-I-switched-/ + http://yannesposito.com/Scratch/en/blog/2010-05-19-How-to-cut-HTML-and-repair-it/ 2012-06-05 - http://yannesposito.com/Scratch/fr/blog/2009-12-14-Git-vs--Bzr/ + http://yannesposito.com/Scratch/fr/latest/ 2012-06-05 - http://yannesposito.com/Scratch/fr/blog/2009-12-06-iphone-call-filter/ + http://yannesposito.com/Scratch/fr/blog/2010-06-17-track-events-with-google-analytics/ 2012-06-05 - http://yannesposito.com/Scratch/fr/blog/10_Synchronize_Custom_WebSite_with_mobileMe/ + http://yannesposito.com/Scratch/fr/blog/2009-10-Focus-vs-Minimalism/ 2012-06-05 - http://yannesposito.com/Scratch/fr/blog/06_How_I_use_git/ + http://yannesposito.com/Scratch/fr/blog/2009-09-replace-all-except-some-part/ 2012-06-05 - http://yannesposito.com/Scratch/en/blog/2010-10-14-Fun-with-wav/ + http://yannesposito.com/Scratch/en/blog/11_Load_Disqus_Asynchronously/ 2012-06-05 - http://yannesposito.com/Scratch/en/blog/2010-06-17-hide-yourself-to-analytics/ + http://yannesposito.com/Scratch/sitemap.xml 2012-06-05 - http://yannesposito.com/Scratch/en/about/old/ + http://yannesposito.com/Scratch/fr/blog/11_Load_Disqus_Asynchronously/ 2012-06-05 - http://yannesposito.com/Scratch/en/softwares/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2010-05-24-Trees--Pragmatism-and-Formalism/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/A-more-convenient-diff/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2010-02-18-split-a-file-by-keyword/ + http://yannesposito.com/Scratch/fr/blog/ 2012-06-05 @@ -269,11 +61,7 @@ 2012-06-05 - http://yannesposito.com/Scratch/en/blog/2010-09-02-base64-and-sha1-on-iPhone/ - 2012-06-05 - - - http://yannesposito.com/Scratch/assets/css/main.css + http://yannesposito.com/Scratch/fr/blog/03_losthighway/03_losthighway_4/ 2012-06-05 @@ -284,96 +72,40 @@ http://yannesposito.com/Scratch/en/blog/2010-07-31-New-style-after-holidays/ 2012-06-05 - - http://yannesposito.com/Scratch/en/blog/04_drm/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2009-11-12-Git-for-n00b/commandes-avancees/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/08_Configure_ssh_to_listen_the_port_443_on_Snow_Leopard/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2010-06-19-jQuery-popup-the-easy-way/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2010-07-05-Cappuccino-and-Web-applications/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/08_Configure_ssh_to_listen_the_port_443_on_Snow_Leopard/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/about/old/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/02_ackgrep/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2010-10-14-Fun-with-wav/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2010-08-31-send-mail-from-command-line-with-attached-file/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/mvc/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/03_losthighway/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/softwares/ypassword/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/softwares/yclock/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/Yesod-excellent-ideas/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/mvc/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2010-05-19-How-to-cut-HTML-and-repair-it/ - 2012-06-05 - http://yannesposito.com/Scratch/fr/blog/2009-11-12-Git-for-n00b/conf-et-install/ 2012-06-05 - http://yannesposito.com/Scratch/en/about/contact/ + http://yannesposito.com/Scratch/fr/blog/03_losthighway/03_losthighway_1/ 2012-06-05 - http://yannesposito.com/Scratch/en/blog/2009-10-Wait-to-hide-a-menu-in-jQuery/ + http://yannesposito.com/Scratch/en/blog/Haskell-Mandelbrot/ 2012-06-05 - http://yannesposito.com/Scratch/fr/blog/04_drm/ + http://yannesposito.com/Scratch/fr/blog/2010-09-02-Use-git-to-calculate-trusted-mtimes/ 2012-06-05 - http://yannesposito.com/Scratch/en/blog/2010-07-09-Indecidabilities/ + http://yannesposito.com/Scratch/fr/blog/2010-07-05-Cappuccino-and-Web-applications/ 2012-06-05 - http://yannesposito.com/Scratch/en/softwares/yaquabubbles/ + http://yannesposito.com/Scratch/fr/blog/2010-03-22-Git-Tips/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2009-10-launch-daemon-from-command-line/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/10_Synchronize_Custom_WebSite_with_mobileMe/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/09_Why_I_didn-t_keep_whosamung-us/ 2012-06-05 @@ -381,31 +113,11 @@ 2012-06-05 - http://yannesposito.com/Scratch/fr/blog/Learn-Vim-Progressively/ + http://yannesposito.com/Scratch/en/blog/2010-10-14-Fun-with-wav/ 2012-06-05 - http://yannesposito.com/Scratch/fr/blog/2010-02-23-When-regexp-is-not-the-best-solution/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2009-09-jQuery-Tag-Cloud/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/Password-Management/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2010-05-24-Trees--Pragmatism-and-Formalism/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2009-10-untaught-git-usage/ + http://yannesposito.com/Scratch/en/blog/2009-11-12-Git-for-n00b/commandes-avancees/ 2012-06-05 @@ -413,15 +125,27 @@ 2012-06-05 - http://yannesposito.com/Scratch/en/blog/05_git_create_remote_branch/ + http://yannesposito.com/Scratch/en/blog/2009-11-12-Git-for-n00b/ 2012-06-05 - http://yannesposito.com/Scratch/fr/blog/Haskell-Mandelbrot/ + http://yannesposito.com/Scratch/fr/blog/2010-05-24-Trees--Pragmatism-and-Formalism/ 2012-06-05 - http://yannesposito.com/Scratch/en/blog/03_losthighway/03_losthighway_3/ + http://yannesposito.com/Scratch/en/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2009-11-12-Git-for-n00b/conf-et-install/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/about/technical_details/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/validation/ 2012-06-05 @@ -432,336 +156,612 @@ http://yannesposito.com/Scratch/fr/softwares/ypassword/iphoneweb/ 2012-06-05 - - http://yannesposito.com/Scratch/en/blog/2010-06-14-multi-language-choices/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/validation/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2009-11-12-Git-for-n00b/conf-et-install/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2009-11-12-Git-for-n00b/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/Higher-order-function-in-zsh/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2010-06-17-track-events-with-google-analytics/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2010-06-15-Get-my-blog-engine/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/03_losthighway/03_losthighway_4/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/Learn-Vim-Progressively/ - 2012-06-05 - http://yannesposito.com/Scratch/fr/blog/Haskell-the-Hard-Way/ 2012-06-05 - - http://yannesposito.com/Scratch/fr/blog/2011-01-03-Happy-New-Year/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2010-10-26-LaTeX-like-macro-and-markdown/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2010-10-10-Secure-eMail-on-Mac-in-few-steps/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2010-01-04-Change-default-shell-on-Mac-OS-X/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2010-08-23-Now-heberged-on-heroku/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/03_losthighway/03_losthighway_1/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2010-02-16-All-but-something-regexp--2-/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/validation/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/SVG-and-m4-fractals/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2010-08-31-send-mail-from-command-line-with-attached-file/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2009-11-12-Git-for-n00b/commandes-avancees/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2010-01-12-antialias-font-in-Firefox-under-Ubuntu/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2010-08-23-Now-heberged-on-heroku/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/07_Screensaver_compilation_option_for_Snow_Leopard/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/about/contact/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/latest/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/about/technical_details/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2009-11-12-Git-for-n00b/Git-pour-quoi-faire/ - 2012-06-05 - http://yannesposito.com/Scratch/fr/blog/2010-09-02-base64-and-sha1-on-iPhone/ 2012-06-05 - http://yannesposito.com/Scratch/fr/blog/2010-06-19-jQuery-popup-the-easy-way/ + http://yannesposito.com/Scratch/fr/blog/2010-02-18-split-a-file-by-keyword/ 2012-06-05 - http://yannesposito.com/Scratch/fr/blog/2009-11-12-Git-for-n00b/Git-pour-quoi-faire/ + http://yannesposito.com/Scratch/fr/blog/2009-10-30-How-to-handle-evil-IE/ 2012-06-05 - http://yannesposito.com/Scratch/fr/blog/2009-10-Focus-vs-Minimalism/ + http://yannesposito.com/Scratch/fr/blog/08_Configure_ssh_to_listen_the_port_443_on_Snow_Leopard/ 2012-06-05 - http://yannesposito.com/Scratch/en/blog/2009-10-Focus-vs-Minimalism/ + http://yannesposito.com/Scratch/en/blog/2010-06-19-jQuery-popup-the-easy-way/ 2012-06-05 - http://yannesposito.com/Scratch/en/blog/2009-10-untaught-git-usage/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2009-10-28-custom-website-synchronisation-with-mobileme--2-/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2010-02-23-When-regexp-is-not-the-best-solution/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2010-10-06-New-Blog-Design-Constraints/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/11_Load_Disqus_Asynchronously/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2010-07-07-CSS-rendering-problems-by-navigator/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2011-04-20-Now-hosted-on-github/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2010-07-31-New-style-after-holidays/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2010-07-07-CSS-rendering-problems-by-navigator/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2009-09-jQuery-Tag-Cloud/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2010-06-15-Get-my-blog-engine/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/Typography-and-the-Web/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2010-03-23-Encapsulate-git/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/softwares/ypassword/web/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/ - 2012-06-05 - - - http://yannesposito.com/Scratch/sitemap.xml - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/05_git_create_remote_branch/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/Haskell-the-Hard-Way/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2009-11-12-Git-for-n00b/c-est-parti-pour-l-aventure/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/A-more-convenient-diff/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/softwares/yaquabubbles/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2010-06-17-hide-yourself-to-analytics/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2009-09-replace-all-except-some-part/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2009-09-Disqus-versus-Intense-Debate--Why-I-switched-/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/about/cv/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/02_ackgrep/ - 2012-06-05 - - - http://yannesposito.com/Scratch/assets/css/dynamic.css - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/programming-language-experience/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2010-03-22-Git-Tips/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2010-02-16-All-but-something-regexp--2-/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/03_losthighway/03_losthighway_4/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2010-10-10-Secure-eMail-on-Mac-in-few-steps/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/2009-12-14-Git-vs--Bzr/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/softwares/ypassword/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/ - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/blog/2011-04-20-Now-hosted-on-github/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/03_losthighway/03_losthighway_1/ - 2012-06-05 - - - http://yannesposito.com/Scratch/en/blog/feed/feed.xml - 2012-06-05 - - - http://yannesposito.com/Scratch/fr/softwares/ypassword/web/ + http://yannesposito.com/Scratch/fr/blog/03_losthighway/ 2012-06-05 http://yannesposito.com/Scratch/fr/blog/2011-01-03-Why-I-sadly-won-t-use-coffeescript/ 2012-06-05 + + http://yannesposito.com/Scratch/en/blog/07_Screensaver_compilation_option_for_Snow_Leopard/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2010-07-07-CSS-rendering-problems-by-navigator/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2010-10-26-LaTeX-like-macro-and-markdown/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/softwares/yclock/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2011-01-03-Why-I-sadly-won-t-use-coffeescript/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/03_losthighway/03_losthighway_2/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/softwares/ypassword/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/mvc/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2009-11-12-Git-for-n00b/commandes-avancees/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2009-10-30-How-to-handle-evil-IE/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2009-09-jQuery-Tag-Cloud/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/A-more-convenient-diff/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2010-06-14-multi-language-choices/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/about/cv/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/about/old/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/softwares/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2010-07-31-New-style-after-holidays/ + 2012-06-05 + http://yannesposito.com/Scratch/fr/blog/2009-11-12-Git-for-n00b/c-est-parti-pour-l-aventure/ 2012-06-05 - http://yannesposito.com/Scratch/en/softwares/yclock/ + http://yannesposito.com/Scratch/fr/blog/SVG-and-m4-fractals/ 2012-06-05 - http://yannesposito.com/Scratch/en/blog/2010-02-15-All-but-something-regexp/ + http://yannesposito.com/Scratch/en/blog/2009-10-untaught-git-usage/ 2012-06-05 - http://yannesposito.com/Scratch/fr/blog/2010-07-05-Cappuccino-and-Web-applications/ + http://yannesposito.com/Scratch/fr/blog/04_drm/ 2012-06-05 - http://yannesposito.com/Scratch/en/blog/2009-09-replace-all-except-some-part/ + http://yannesposito.com/Scratch/en/blog/2010-10-10-Secure-eMail-on-Mac-in-few-steps/ 2012-06-05 - http://yannesposito.com/Scratch/en/blog/programming-language-experience/ + http://yannesposito.com/Scratch/en/blog/2010-03-22-Git-Tips/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/about/technical_details/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/06_How_I_use_git/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/programming-language-experience/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2009-12-06-iphone-call-filter/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2009-11-12-Git-for-n00b/comprendre/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2010-09-02-base64-and-sha1-on-iPhone/ 2012-06-05 http://yannesposito.com/Scratch/fr/blog/03_losthighway/03_losthighway_2/ 2012-06-05 + + http://yannesposito.com/Scratch/en/about/cv/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/validation/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2010-01-04-Change-default-shell-on-Mac-OS-X/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/09_Why_I_didn-t_keep_whosamung-us/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2010-10-14-Fun-with-wav/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/01_nanoc/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2010-01-12-antialias-font-in-Firefox-under-Ubuntu/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2009-10-How-to-preload-your-site-with-style/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2010-03-23-Encapsulate-git/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/Learn-Vim-Progressively/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2011-04-20-Now-hosted-on-github/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2011-01-03-Happy-New-Year/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2010-08-31-send-mail-from-command-line-with-attached-file/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2010-09-02-Use-git-to-calculate-trusted-mtimes/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2010-02-23-When-regexp-is-not-the-best-solution/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2010-07-07-CSS-rendering-problems-by-navigator/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/02_ackgrep/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/SVG-and-m4-fractals/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/feed/feed.xml + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2010-10-10-Secure-eMail-on-Mac-in-few-steps/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2009-10-untaught-git-usage/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/03_losthighway/03_losthighway_3/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2010-01-12-antialias-font-in-Firefox-under-Ubuntu/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2009-09-Disqus-versus-Intense-Debate--Why-I-switched-/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/Typography-and-the-Web/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/softwares/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2010-06-15-Get-my-blog-engine/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/03_losthighway/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2010-05-24-Trees--Pragmatism-and-Formalism/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/05_git_create_remote_branch/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2009-10-Wait-to-hide-a-menu-in-jQuery/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2010-06-17-hide-yourself-to-analytics/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2009-09-jQuery-Tag-Cloud/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2010-08-23-Now-heberged-on-heroku/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2009-10-Focus-vs-Minimalism/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2011-01-03-Happy-New-Year/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2010-02-16-All-but-something-regexp--2-/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/latest/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2009-11-12-Git-for-n00b/comprendre/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/06_How_I_use_git/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/05_git_create_remote_branch/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/08_Configure_ssh_to_listen_the_port_443_on_Snow_Leopard/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/Learn-Vim-Progressively/ + 2012-06-05 + + + http://yannesposito.com/Scratch/assets/css/dynamic.css + 2012-06-05 + + + http://yannesposito.com/Scratch/en/softwares/ypassword/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/Password-Management/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2010-10-26-LaTeX-like-macro-and-markdown/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2010-08-23-Now-heberged-on-heroku/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2010-06-15-Get-my-blog-engine/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2009-11-12-Git-for-n00b/Git-pour-quoi-faire/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/Higher-order-function-in-zsh/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/02_ackgrep/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/01_nanoc/ + 2012-06-05 + + + http://yannesposito.com/Scratch/assets/css/main.css + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2010-07-09-Indecidabilities/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/softwares/yclock/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2010-06-19-jQuery-popup-the-easy-way/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/10_Synchronize_Custom_WebSite_with_mobileMe/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/Password-Management/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2009-09-replace-all-except-some-part/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2010-02-16-All-but-something-regexp--2-/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/Haskell-OpenGL-Mandelbrot/ + 2012-06-11 + + + http://yannesposito.com/Scratch/fr/blog/03_losthighway/03_losthighway_3/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/about/contact/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/mvc/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2009-12-14-Git-vs--Bzr/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/softwares/ypassword/iphoneweb/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/softwares/yaquabubbles/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2010-05-17-at-least-this-blog-revive/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2010-06-17-hide-yourself-to-analytics/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/Haskell-Mandelbrot/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2010-10-06-New-Blog-Design-Constraints/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2010-02-15-All-but-something-regexp/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2009-09-Disqus-versus-Intense-Debate--Why-I-switched-/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2010-06-14-multi-language-choices/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2010-01-04-Change-default-shell-on-Mac-OS-X/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/04_drm/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/rss/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/Yesod-tutorial-for-newbies/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2009-10-28-custom-website-synchronisation-with-mobileme--2-/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2011-04-20-Now-hosted-on-github/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/Haskell-the-Hard-Way/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/softwares/ypassword/web/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/softwares/yaquabubbles/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/about/contact/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2009-10-How-to-preload-your-site-with-style/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2010-02-15-All-but-something-regexp/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/Typography-and-the-Web/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/Haskell-OpenGL-Mandelbrot/ + 2012-06-11 + + + http://yannesposito.com/Scratch/fr/blog/2009-12-14-Git-vs--Bzr/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2010-02-18-split-a-file-by-keyword/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2010-10-06-New-Blog-Design-Constraints/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/Yesod-excellent-ideas/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/A-more-convenient-diff/ + 2012-06-05 + http://yannesposito.com/Scratch/en/blog/2010-06-17-track-events-with-google-analytics/ 2012-06-05 + + http://yannesposito.com/Scratch/en/blog/03_losthighway/03_losthighway_4/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/Higher-order-function-in-zsh/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2010-03-23-Encapsulate-git/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/softwares/ypassword/web/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/Yesod-excellent-ideas/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/programming-language-experience/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/03_losthighway/03_losthighway_1/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/Yesod-tutorial-for-newbies/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2009-11-12-Git-for-n00b/c-est-parti-pour-l-aventure/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2010-07-09-Indecidabilities/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2010-05-17-at-least-this-blog-revive/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/2009-11-12-Git-for-n00b/ + 2012-06-05 + + + http://yannesposito.com/Scratch/fr/blog/07_Screensaver_compilation_option_for_Snow_Leopard/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2009-10-28-custom-website-synchronisation-with-mobileme--2-/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2009-11-12-Git-for-n00b/Git-pour-quoi-faire/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2010-08-31-send-mail-from-command-line-with-attached-file/ + 2012-06-05 + + + http://yannesposito.com/Scratch/en/blog/2010-07-05-Cappuccino-and-Web-applications/ + 2012-06-05 +