hglmandel/05_Mandelbulb/Mandelbulb.lhs
Yann Esposito (Yogsototh) 6cdc1ff975 Mandelbulb
2012-05-22 09:36:27 +02:00

144 lines
4.8 KiB
Text

## Functional organization?
Some points:
1. OpenGL and GLUT are linked to the C library.
In particular the `mainLoop` function is a direct link to the C library (FFI).
This function if so far from the pure spirit of functional languages.
Could we make this better?
We will have two choices, or create our own `mainLoop` function to make it more functional.
Or deal with the imperative nature of the GLUT `mainLoop` function.
As a goal of this article is to understand how to deal with existing library and particularly the one coming from impertive language we will continue to use the `mainLoop` function.
2. Or main problem come from user interaction.
If you ask the Internet, about how to deal with user interaction with a functional paradigm, the main answer is to use _functional reactive programming_ (FRP).
I read very few about FRP, and I might be completely wrong when I say that it is about creating a DSL where atoms are time functions.
While I'm writting these lines, I don't know if I'll do something looking close to that.
For now I'll simply try to resolve the first problem.
Then here is how I imagine things should go.
First, what the main loop should look like:
<code class="haskell">
functionalMainLoop =
Read user inputs and provide a list of actions
Apply all actions to the World
Display one frame
repetere aeternum
</code>
Clearly, ideally we should provide only three parameters to this main loop function:
- an initial World state
- a mapping between the user interaction and function which modify the world
- a function which transform the world without user interaction.
The mapping between user input and actions.
> import YGL -- Most the OpenGL Boilerplate
> import Mandel -- The 3D Mandelbrot maths
> -- Centralize all user input interaction
> inputActionMap :: InputMap World
> inputActionMap = inputMapFromList [
> (Press 'k' , rotate xdir 5)
> ,(Press 'i' , rotate xdir (-5))
> ,(Press 'j' , rotate ydir 5)
> ,(Press 'l' , rotate ydir (-5))
> ,(Press 'o' , rotate zdir 5)
> ,(Press 'u' , rotate zdir (-5))
> ,(Press 'f' , translate xdir 0.1)
> ,(Press 's' , translate xdir (-0.1))
> ,(Press 'e' , translate ydir 0.1)
> ,(Press 'd' , translate ydir (-0.1))
> ,(Press '+' , zoom 1.1)
> ,(Press '-' , zoom 0.9)
> ]
The type of each couple should be of the form
`(user input, f)` where (in a first time) `f:World -> World`.
It means, the user input will transform the world state.
And of course a type design the World State:
> -- I prefer to set my own name for these types
> data World = World {
> angle :: Point3D
> , scale :: Scalar
> , position :: Point3D
> , shape :: Function3D
> }
> instance DisplayableWorld World where
> camera w = Camera {
> camPos = position w,
> camDir = angle w,
> camZoom = scale w }
> objects w = [XYFunc (shape w)]
With all associated functions:
> xdir :: Point3D
> xdir = makePoint3D (1,0,0)
> ydir :: Point3D
> ydir = makePoint3D (0,1,0)
> zdir :: Point3D
> zdir = makePoint3D (0,0,1)
>
> rotate :: Point3D -> Scalar -> World -> World
> rotate dir angleValue world = world {
> angle = angleValue -*< dir
> }
>
> translate :: Point3D -> Scalar -> World -> World
> translate dir len world = world {
> position = position world -+< (len -*< dir)
> }
>
> zoom :: Scalar -> World -> World
> zoom z world = world {
> scale = z * scale world }
- [`YBoiler.hs`](code/04_Mandelbulb/YBoiler.hs), the 3D rendering
- [`Mandel`](code/04_Mandelbulb/Mandel.hs), the mandel function
- [`ExtComplex`](code/04_Mandelbulb/ExtComplex.hs), the extended complexes
>
> -- yMainLoop takes two arguments
> -- the title of the window
> -- a function from time to triangles
> main :: IO ()
> main = yMainLoop "3D Mandelbrot" inputActionMap initialWorld
>
> -- We initialize the world state
> -- then angle, position and zoom of the camera
> -- And the shape function
> initialWorld :: World
> initialWorld = World {
> angle = makePoint3D (0,0,0)
> , position = makePoint3D (0,0,0)
> , scale = 1
> , shape = shapeFunc
> }
>
> shapeFunc :: Function3D
> shapeFunc x y =
> let
> depth = 1
> z = findMaxOrdFor (ymandel x y) 0 depth (truncate $ log depth)
> in
> if z == 0
> then Nothing
> else Just z
>
>
> findMaxOrdFor :: (Fractional a,Num a,Num b,Eq b) =>
> (a -> b) -> a -> a -> Int -> a
> findMaxOrdFor _ minval maxval 0 = (minval+maxval)/2
> findMaxOrdFor func minval maxval n =
> if func medpoint /= 0
> then findMaxOrdFor func minval medpoint (n-1)
> else findMaxOrdFor func medpoint maxval (n-1)
> where medpoint = (minval+maxval)/2
>
> ymandel :: Point -> Point -> Point -> Point
> ymandel x y z = fromIntegral (mandel x y z 64) / 64