hglmandel/05_Mandelbulb/Mandelbulb.lhs

144 lines
4.7 KiB
Text
Raw Normal View History

2012-05-15 13:21:18 +00:00
## 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 transorm the world without user interaction.
2012-05-15 13:21:18 +00:00
The mapping between user input and actions.
> import YGL -- Most the OpenGL Boilerplate
> import Mandel -- The 3D Mandelbrot maths
> -- Centralize all user input interaction
2012-05-21 12:41:55 +00:00
> inputActionMap :: InputMap World
2012-05-15 13:21:18 +00:00
> inputActionMap = inputMapFromList [
2012-05-22 07:36:27 +00:00
> (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))
2012-05-21 12:41:55 +00:00
> ,(Press '+' , zoom 1.1)
> ,(Press '-' , zoom 0.9)
2012-05-15 13:21:18 +00:00
> ]
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
2012-05-21 12:41:55 +00:00
> , scale :: Scalar
2012-05-15 13:21:18 +00:00
> , position :: Point3D
> , shape :: YObject
2012-05-15 13:21:18 +00:00
> }
2012-05-21 12:41:55 +00:00
> instance DisplayableWorld World where
> camera w = Camera {
> camPos = position w,
> camDir = angle w,
> camZoom = scale w }
> objects w = [shape w]
2012-05-21 12:41:55 +00:00
2012-05-15 13:21:18 +00:00
With all associated functions:
2012-05-21 12:41:55 +00:00
> xdir :: Point3D
> xdir = makePoint3D (1,0,0)
> ydir :: Point3D
> ydir = makePoint3D (0,1,0)
> zdir :: Point3D
> zdir = makePoint3D (0,0,1)
2012-05-15 13:21:18 +00:00
>
> rotate :: Point3D -> Scalar -> World -> World
> rotate dir angleValue world =
> world {
> angle = (angle world) + (angleValue -*< dir) }
2012-05-15 13:21:18 +00:00
>
2012-05-21 12:41:55 +00:00
> translate :: Point3D -> Scalar -> World -> World
> translate dir len world =
> world {
> position = (position world) + (len -*< dir) }
2012-05-15 13:21:18 +00:00
>
2012-05-21 12:41:55 +00:00
> zoom :: Scalar -> World -> World
2012-05-15 13:21:18 +00:00
> zoom z world = world {
2012-05-21 12:41:55 +00:00
> scale = z * scale world }
2012-05-15 13:21:18 +00:00
- [`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 ()
2012-05-21 12:41:55 +00:00
> main = yMainLoop "3D Mandelbrot" inputActionMap initialWorld
2012-05-15 13:21:18 +00:00
>
> -- We initialize the world state
> -- then angle, position and zoom of the camera
> -- And the shape function
2012-05-21 12:41:55 +00:00
> initialWorld :: World
> initialWorld = World {
> angle = makePoint3D (0,1,0)
2012-05-21 12:41:55 +00:00
> , position = makePoint3D (0,0,0)
> , scale = 0.2
> , shape = XYSymFunc shapeFunc
2012-05-21 12:41:55 +00:00
> }
>
> shapeFunc :: Function3D
> shapeFunc x y =
> let
> z = findMaxOrdFor (ymandel x y) 0 1 20
2012-05-21 12:41:55 +00:00
> in
> if z < 0.000001
2012-05-22 07:36:27 +00:00
> then Nothing
> else Just z
2012-05-15 13:21:18 +00:00
>
>
2012-05-21 15:03:17 +00:00
> findMaxOrdFor :: (Fractional a,Num a,Num b,Eq b) =>
> (a -> b) -> a -> a -> Int -> a
> findMaxOrdFor _ minval maxval 0 = (minval+maxval)/2
2012-05-15 13:21:18 +00:00
> findMaxOrdFor func minval maxval n =
2012-05-21 12:41:55 +00:00
> if func medpoint /= 0
2012-05-15 13:21:18 +00:00
> then findMaxOrdFor func minval medpoint (n-1)
> else findMaxOrdFor func medpoint maxval (n-1)
> where medpoint = (minval+maxval)/2
>
2012-05-21 15:03:17 +00:00
> ymandel :: Point -> Point -> Point -> Point
> ymandel x y z = fromIntegral (mandel x y z 64) / 64