2.3.9 While-loop tutorial

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).

2.3.9.1 What a while-loop is and what it is not

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).

2.3.9.2 How does a single while-loop work?

The while-loop works like this:

  1. If the condition between the parentheses evaluates to false, jump to the command after the #end statement. If the condition evaluates to true, just continue normally.
  2. At the #end statement jump to the #while statement and start again.

That is:

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.

2.3.9.3 How do I make a while-loop?

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?

  1. First POV-Ray sets the value 1 to 'Index'.
  2. Then it sees the #while statement and evaluates what is between the parentheses: Index <= 10
  3. As 'Index' has the value of 1 and 1 <= 10, the condition evaluates to true.
  4. So, it just continues normally. It executes the commands following the #while statement (denoted in the above example as "(some commands here)").
  5. Then it arrives normally to the last #declare command in the block. This causes the value 2 to be assigned to 'Index'.
  6. Now it arrives the the #end command and so it just jumps to the #while.
  7. After that it executes the steps 2-6 again because also 2 is less or equal to 10.
  8. After this has been done 10 times, the value 11 is assigned to 'Index' in the last command of the block.
  9. Now, when POV-Ray evaluates the condition it sees that it is false (because 11 is not less or equal to 10). This causes POV-Ray to jump to the command after the #end statement.
  10. The net effect of all this is that POV-Ray looped 10 times and the 'Index' variable got successive values from 1 to 10 along the way.

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).

2.3.9.4 What is a condition and how do I make one?

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.

2.3.9.5 What about loop types other than simple for-loops?

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

2.3.9.6 What about nested loops?

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.

2.3.9.7 Mixed-type nested loops

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:

2.3.9.8 Other things to note

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.