## Theory
Normal "fractals" as Julia sets or Mandelbrot sets iterate a fixed formula until some condition evaluates to true (then a pixel is said to be "outside") or the maximum number of iterations has been reached (then a pixel is said to be "inside").
The result can be colored in thousands of ways, but the basic algorithm always stays the same. Take an initial value, iterate, test condition, iterate again or stop.
- Start with an initial value (a quaternion number)
- Iterate using the iteration formula
- Test the condition:
- If it is true, the pixel is "outside"
- If it is false, continue
- Has the maximum number of iterations been reached? If yes, stop, pixel is "inside", if not, continue...
Simple, isn't it? So why are most of the fractal generators only calculating Julia and Mandelbrot sets using complex numbers? The reason is: Basically Quaternions are (at least) 4 dimensional objects! This means: - They cannot be drawn directly: 2 dimensional objects can be drawn, the window on the screen specifies a part of the complex number plane, we color each pixel to show what has happened.
- The maximum number of dimensions for visualization is 3. So we have to define a 3 dimensional sub space from the 4 dimensional quaternion space and draw the fractal. The result then is a true 3D object,
complicated structured, with holes, mountains, hills, valleys, cliffs, etc.
It is much more difficult to draw a true 3D object (smoothing, resampling, light model and all that stuff) than just to draw a pixel and color it... - The calculation is awfully slow: Assume you want to calculate a basic 320x200 image of a complex Mandelbrot set (we all know it...). Then you have to calculate 320*200=6400 pixels: Each pixel iterates a formula on average, say, 60 times. That means, the
main iteration loop gets executed 6400*60=384 000 times!
Now consider a three dimensional space, a quaternion in it: In order to draw it we have to calculate our 60 iterations (on average) not only 320*200 times, but 320*200*250 times (for each pixel in our 3D space, having a resolution of 320 in x-direction, 200 in y-direction and 250 in z-direction)! I.e. the calculation needs 250 times as long! And besides that, calculations using quaternions are much more complicated and time consuming than calculations using complex numbers: Multiplying two quaternions for example needs 4 times as long as multiplying two complex numbers.
You see, there are many reasons why most fractal generators avoid calculating fractals using quaternion numbers... ChaosPro uses several techniques in order to speed up the process, but please, *please* be not disappointed: Calculating true 3D objects using such complicated math is slow. So lets start explaining how ChaosPro calculates quaternions: At first we must define a three dimensional subspace from the 4D - Quaternion space: Generally one can define all hyper planes from any n-dimensional space by two values: - a base point
- a normal vector
A hyper plane of an n-dimensional space is an (n-1) - dimensional sub space of it. And that's what we want. So by specifying a 4D-base point and a 4D-normal vector we can express all 3D sub spaces from that 4 dimensional space: Our sub space consists of all points which form a rectangular angle to the normal vector. And all points are then translated by the base point. Seems strange? Well, the same method can be applied to any space, so lets consider the following: We want to specify any 1 dimensional object from a 2D-space, or to speak more clear: We want all lines from a plane. Indeed, we can express any line by providing a normal vector and a base point (==> translation), as displayed in the following image: All three lines can be expressed by specifying a base point (o1, o2, o3) and a normal vector. The representation is not unique: As you can see, the base point can be anywhere on the line in question. The same principle allows us to define areas (2D) in a 3D space, or, and that's what we want, 3D spaces lying in a 4D-space. So we now should be able to specify a 3D space lying in a 4D-quaternion space. Of course, currently it is "lying" in it, i.e. it has dimension 3, but all points of it have 4 components. The next step is to define a base of this 3D space. This base of course consists of three vectors in the 4D-quaternion space. Lets call these vectors b1, b2 and b3. The base point O1 defined before specifies the "null point" from our 3D space: We now have parametrized our 3D space: We can express all points P in our 3D space as P=O1+x*b1+y*b2+z*b3, or, abbreviated: (x,y,z) And so we now are completely in a (virtual) 3D space: - (x,y,z)=(0,0,0)= O1 + 0*b1 + 0*b2 + 0*b3 = O1
- (x,y,z)=(1,0,0)= O1 + 1*b1 + 0*b2 + 0*b3 = O1 + b1
- (x,y,z)=(0,1,0)= O1 + 0*b1 + 1*b2 + 0*b3 = O1 + b2
- (x,y,z)=(0,0,1)= O1 + 0*b1 + 0*b2 + 1*b3 = O1 + b3
We start in a 3D space, and whenever we need the corresponding 4D quaternion value, we simply calculate P as O1+x*b1+y*b2+z*b3. So we can calculate a quaternion as follows: An observer (i.e. YOU) is located anywhere. You can adjust his position. So for example he is at (3,0,0). You look at some point in the scene, lets say onto (0,0,0). Your complete position (like rotation, where is "top") can be specified, too. So you look at the scene and the fractal window should display what you see: So we now have to scan the whole world seen by you, pixel for pixel, in order to determine whether it belongs to the quaternion fractal or not. Lets make an example for the pixel (0.3,-0.2,1.1), assuming some defaults. We must iterate a quaternion formula (lets say z^2+c), and at first we must initialize z to the current pixel. So: - 3D-Point: (0.3,-0.2,1.1) ==> z= O1 + 0.3*b1 - 0.2*b2 + 1.1*b3
- for i=1 to Maxiter
- z=z^2+c
- if |z|>4 then Bailout, pixel (0.3,-0.2,1.1) is "outside"
- Next
- If Maxit has been reached, pixel (0.3,-0.2,1.1) is "inside"
After all points in view direction have been examined, draw the inner region, i.e. the object defined by all points which "belong" to the quaternion. You may ask why we draw the "inner" object: The most interesting part of the Mandelbrot set was the outer region. Well, the reason is simple: The outer region is unbound, i.e. there is a (small) object defined by the inner region, and all other pixels are "outside". To make a more realistic example: Can YOU see a small bird when you are "inside" a wall? Most probably you only would see "grey" ;-) |