2.3.8 Making Animations |
POV-Ray 3.6 for UNIX documentation 2.3.9 While-loop tutorial |
2.3.10 SDL tutorial: A raytracer |
Usually people who have never programmed have great difficulties understanding how simple while-loops work and how they should be used. When you get into nested loops, the problem is even worse.
Sometimes even people who have programmed a bit with some language get confused with POV-Ray's while-loops. This usually happens when they have only used a for-loop which in itself has an index variable (which is often even incremented automatically).
A while-loop in POV-Ray is just a control structure which tells POV-Ray to loop a command block while the specified condition is true (ie. until it gets false).
That is, a while-loop is like this:
#while(condition) ... #end
The commands between #while
and #end
are run over and over as long as the condition
evaluates to true.
A while-loop is not a for-loop nor any kind of loop which has an index variable by itself (which may be incremented automatically in each loop).
The while-loop does not care what the conditions are between the parentheses (as long as they
evaluate to some value) or what does the block between #while
and #end
contain. It will just
execute that block until the condition becomes false.
The while-loop does not do anything else. You can think about it as a kind of "dumb" loop, which does not do anything automatically (and this is not necessarily a bad thing).
The while-loop works like this:
#end
statement. If the condition evaluates to true, just continue normally.
#end
statement jump to the #while
statement and start again.
That is:
#while
statement it evaluates the condition between parentheses.
#end
statement.
#end
statement POV-Ray will just jump back to the corresponding #while
-statement
and then continue normally (ie. testing the condition and so on).
Note that nowhere there is any mention about any index variable or anything else that could be used to automatically end the loop or whatever. As said, it is just a "dumb" loop that continues forever if necessary, only testing the statement between the parentheses (but it is not interested in what it is, only in its evaluated value).
Although one could easily think that this kind of "dumb" loop is bad and it should be more "intelligent" and better, the fact is that this kind of "dumb" loop is actually a lot more flexible and versatile. It allows you to make things not possible or very difficult to do with an "intelligent" for-loop with automatic index variables.
It depends on what you are trying to make.
The most common usage is to use it as a simple for-loop, that is, a loop which loops a certain number of times (for example 10 times) with an index value getting successive values (for example 1, 2, 3, ..., 10).
For this you need to first declare your index identifier with the first value. For example:
#declare Index = 1;
Now you want to loop 10 times. Remember how the condition worked: The while-loop loops as long as the condition is true. So it should loop as long as our 'Index' identifier is less or equal to 10:
#while(Index <= 10)
When the 'Index' gets the value 11 the loop ends, as it should.
Now we only have to add 1 to 'Index' at each loop, so we should do it at the end of the loop, thus getting:
#declare Index = 1; #while(Index <= 10) (some commands here) #declare Index = Index + 1; #end
The incrementation before the #end
is important. If we do not do it, 'Index' would always have the
value 1 and the loop would go forever since 1 is always less or equal to 10.
What happens here?
#while
statement and evaluates what is between the parentheses: Index <= 10
#while
statement (denoted in
the above example as "(some commands here)").
#end
command and so it just jumps to the #while
.
#end
statement.
If you read carefully the above description you will notice that the looping is done in a quite "dumb" way, that is, there is no higher logic hidden inside the loop structure. In fact, POV-Ray does not have the slightest idea how many times the loop is executed and what variable is used to count the loops. It just follows orders.
The higher logic in this type of loop is in the combination of commands we wrote. The effect of this combination is that the loop works like a simple for-loop in most programming languages (like BASIC, etc).
A condition is an expression that evaluates to a boolean value (ie. true or false) and is used in POV-Ray in #while
-loops
and #if
-statements.
A condition is mainly a comparison between two values (although there are also some other ways of making a condition, but that is not important now). For example:
1 < 2 is true 1 > 2 is false 1 = 1 is true 1 = 2 is false and so on.
Usually it makes no sense to make comparisons like those. However, when comparing identifiers with some value or two identifiers together it starts to be very useful. Consider this:
#if(version < 3.1) #error "Wrong version!" #end
If the identifier called 'version' has a value which is less than 3.1 the #error
line will be
executed. If 'version' has a value which is 3.1 or greater, the #error
line is just skipped.
You can combine conditions together with the boolean operators & (and) and | (or). You can also invert the value of a condition with ! (not).
For example, if you want something to be done when 'a' is less than 10 and 'b' is greater or equal to 20, the condition would be:
a<10 & b>=20
For more information about these comparison operators, see the 'Float operators' section of the POV-Ray documentation.
As POV-Ray does not care what the condition is and what we are using to make that condition, we can use the while-loop in many other ways.
For example, this is a typical use of the while-loop which is not a simple for-loop:
#declare S = seed(0); #declare Point = <2*rand(S)-1, 2*rand(S)-1, 2*rand(S)-1>; #while(vlength(Point) > 1) #declare Point = <2*rand(S)-1, 2*rand(S)-1, 2*rand(S)-1>; #end
What we are doing here is this: Take a random point between <-1, -1, -1> and <1, 1, 1> and if it is not inside the unit sphere take another random point in that range. Do this until we get a point inside the unit sphere.
This is not an unrealistic example since it is very handy.
As we see, this has nothing to do with an ordinary for-loop:
As we can see, a while-loop can also be used for a task of type "calculate a value or some values until the result is inside a specified range" (among many other tasks).
By the way, there is a variant of this kind of loop where the task is: "Calculate a value until the result is inside a specified range, but make only a certain number of tries. If the value does not get inside that range after that number of tries, stop trying". This is used when there is a possibility for the loop for going on forever.
In the above example about the point inside the unit sphere we do not need this because the random point will surely hit the inside of the sphere at some time. In some other situations, however, we cannot be so sure.
In this case we need a regular index variable to count the number of loops. If we have made that amount of loops then we stop.
Suppose that we wanted to modify our point searching program to be completely safe and to try only up to 10 times. If the point does not hit the inside of the sphere after 10 tries, we just give up and take the point <0,0,0>.
#declare S = seed(0); #declare Point = <2*rand(S)-1, 2*rand(S)-1, 2*rand(S)-1>; #declare Index = 1; #while(Index <= 10 & vlength(Point) > 1) #declare Point = <2*rand(S)-1, 2*rand(S)-1, 2*rand(S)-1>; #declare Index = Index + 1; #end #if(vlength(Point) > 1) #declare Point = <0,0,0> #end
What did we do?
Btw, sometimes it is not convenient to make the test again in the #if
statement. There is another way
of determining whether the loop bailed out without successful termination or not: Since the loop ends when the 'Index'
gets the value 11, we can use this to test the successfulness of the loop:
#if(Index = 11) (loop was not successful) #end
Even when one masters simple loops, nested loops can be a frightening thing (or at least hard to understand).
Nested loops are used for example for creating a 2D array of objects (with rows and columns of objects), etc. For example if you want to create a 10x20 array of spheres in your scene, a nested loop is the tool for it.
There is nothing special about nested loops. You only have to pay attention to where you initialize and update your index variables.
Let's recall the form of a simple for-loop:
#declare Index = initial_value; #while(Index <= final_value) [Something here] #declare Index = Index + index_step; #end
The [Something here] part can be anything. If it is another while-loop, then we have nested loops. The inner loop should have the same structure as the outer one.
Note that proper indentation helps us distinguishing between the loops. It is always a good idea to use a good indentation scheme:
#declare Index1 = initial_value1; #while(Index1 <= final_value1) #declare Index2 = initial_value2; #while(Index2 <= final_value2) [Something here] #declare Index2 = Index2 + index2_step; #end #declare Index1 = Index1 + index1_step; #end
It is a common mistake for beginners to break this structure. For example it is common to declare the 'Index2'
before the first #while
. This breaks the for-loop structure and thus does not work. If you follow step by
step what POV-Ray does, as explained earlier, you will see why it does not work. Do not mix the structures of the
inner and the outer loops together or your code will simply not work as expected.
So, if we want to make our 10x20 array of spheres, it will look like this:
#declare Index1 = 0; #while(Index1 <= 9) #declare Index2 = 0; #while(Index2 <= 19) sphere { <Index1, Index2, 0>, .5 } #declare Index2 = Index2 + 1; #end #declare Index1 = Index1 + 1; #end
Note how we now start from 0 and continue to 9 in the outer loop and from 0 to 19 in the inner loop. This has been done to get the sphere array start from the origin (instead of starting from <1, 1, 0>). Of course we could have made the 'Index1' and 'Index2' go from 1 to 10 and from 1 to 20 respectively and then created the sphere in this way:
sphere { <Index1-1, Index2-1, 0>, .5 }
Although you should not mix the loop structures together, you can perfectly use the values of the outer loop in the
inner loop (eg. in its condition). For example, if we wanted to create a triangular array of spheres instead of a
rectangular one (that is, we create only half of the spheres), we could have made the inner #while
like
this:
#while(Index2 < Index1*2)
('Index2' will go from 0 to the value of 'Index1' multiplied by 2.)
There is no reason why we should limit ourselves to just two nested loops. There is virtually no limit how many loops you can nest. For example, if we wanted to create a box-shape filled by spheres rows, colums and depth, we just make three nested loops, one for the x-axis, another for the y-axis and the third for the z-axis.
It is perfectly possible to put a for-loop inside a non-for-loop or vice-versa. Again, you just have to be careful (with experience it gets easier).
For example, suppose that we want to create 50 spheres which are randomly placed inside the unit sphere.
So the distinction is clear: First we need a loop to create 50 spheres (a for-loop type suffices) and then we need another loop inside it to calculate the location of the sphere. It will look like this:
#declare S = seed(0); #declare Index = 1; #while(Index <= 50) #declare Point = <2*rand(S)-1, 2*rand(S)-1, 2*rand(S)-1>; #while(vlength(Point) > 1) #declare Point = <2*rand(S)-1, 2*rand(S)-1, 2*rand(S)-1>; #end sphere { Point, .1 } #declare Index = Index + 1; #end
There are some important things to note in this specific example:
There is no reason why the index value in your simple for-loop should step one unit at a time. Since the while-loop does not care how the index changes, you can change it in whichever way you want. Eg:
#declare Index = Index - 1; Decrements the index (be careful with the while loop condition) #declare Index = Index + 0.2; Increases by steps of 0.2 #declare Index = Index * 2; Doubles the value of the index at each step. etc.
- Be careful where you put your while-loop.
I have seen this kind of mistake more than once:
#declare Index = 1; #while(Index <= 10) blob { threshold 0.6 sphere { <Index, 0, 0>, 2, 1 } } #declare Index = Index + 1; #end
You will probably see immediately the problem.
This code creates 10 blobs with one component each. It does not seem to make much sense. Most probably the user wanted to make one blob with 10 components.
Why did this mistake happen? It may be that the user (more or less subconsciously) thought that the while-loop must be the outermost control structure and does not realize that while-loops can be anywhere, also inside objects (creating subcomponents or whatever).
The correct code is, of course:
blob { threshold 0.6 #declare Index = 1; #while(Index <= 10) sphere { <Index, 0, 0>, 2, 1 } #declare Index = Index + 1; #end }
The essential difference here is that it is only the sphere code which is run 10 times instead of the whole blob code. The net result is the same as if we had written the sphere code 10 times with proper values of 'Index'.
Be also careful with the placement of the braces. If you put them in the wrong place you can end up accidentally repeating an opening or a closing brace 10 times. Again, a proper indentation usually helps a lot with this (as seen in the above example).
- Tip: You can use while-loops in conjunction with arrays to automatize the creation of long lists of elements with differing data.
Imagine that you are making something like this:
color_map { [0.00 rgb <.1,1,.6>] [0.05 rgb <.8,.3,.6>] [0.10 rgb <.3,.7,.9>] [0.15 rgb <1,.7,.3>] ... and so on
It is tedious to have to write the same things over and over just changing the index value and the values in the vector (even if you use copy-paste to copy the lines).
There is also one very big problem here: If you ever want to add a new color to the color map or remove a color, you would have to renumber all the indices again, which can be extremely tedious and frustrating.
Would not it be nice to automatize the creation of the color map so that you only have to write the vectors and that is it?
Well, you can. Using a while-loop which reads an array of vectors:
#declare MyColors = array[20] { <.1,1,.6>, <.8,.3,.6>, <.3,.7,.9>, <1,.7,.3>, ... } ... color_map { #declare LastIndex = dimension_size(MyColors, 1)-1; #declare Index = 0; #while(Index <= LastIndex) [Index/LastIndex rgb MyColors[Index]] #declare Index = Index + 1; #end }
Now it is easy to add, remove or modify colors in your color map. Just edit the vector array (remembering to change its size number accordingly) and the while-loop will automatically calculate the right values and create the color map for you.
2.3.8 Making Animations | 2.3.9 While-loop tutorial | 2.3.10 SDL tutorial: A raytracer |