HowTo:Use conditional structures

From POV-Wiki
Jump to navigation Jump to search

Conditional constructs in POV-Ray are scripting features which perform different computations or actions depending on whether a condition evaluates to true or false.


The #if, #else, #end construct

The simplest conditional directive is the #if directive. This evaluates an expression, which must evaluate to a floating point number (often an integer value). This floating point number is interpreted as a boolean value, that is to say that if the expression evaluates to '0', it is considered 'false' and if it evaluates to anything else it is considered 'true'. If the condition is true then directives between the #if statement and a corresponding #else or #end statement are evaluated. Note: extremely small values of about 1e-10 are considered zero (false) as a result of floating point number accuracy limits.

The #else directive is optional but, if specified and the conditional expression was false, then directives between the #else and the corresponding #end statement are evaluated.

For example:

#declare CameraDistance=25.8;
 
 ... Definition of some complex object that looks like a sphere at great distances. ...
 
#if (CameraDistance>100)
   sphere { <0,0,0>, 1.2 pigment {rgb 1}}
#else
   object {MyComplexObject}
#end

Here the 'greater than' comparitive operator '>' is used to see whether the value of the CameraDistance variable is greater than 100. In this example, the expression 'CameraDistance>100' evaluates to 0 ('false') because the CameraDistance variable is less than 100.

Conditional Conjunctions

You can combine conditional terms to build more complex conditional expressions using '&' (And), '|' (Or) and '!' (Not). Use brackets to control the sequence of evaluation. You can also use POV-Ray functions within terms so long as they can be resolved to numeric values. The built-in float identifiers on, off, yes, no, true, and false are designed for use as boolean constants and simply evaluate to 0 (off, no and false) or 1 (on, yes and true).

Here's an example that might take a little thinking about:

#declare Clothing="Jeans";
#declare JeansPermitted=false;
// Check to see if Jeans are permitted
#if (strcmp(Clothing,"Jeans")=0 & !JeansPermitted)
   #debug "Sorry. Jeans are not permitted\n"
#end

In this example, the strcmp function returns a zero because there is no difference between the two strings it has compared. The term 'strcmp(Clothing,"Jeans")=0' therefore returns a 1 (this term is true). The '!' (Not) symbol is used to check for the condition where jeans are not permitted. The '&' symbol is used to combine the two and to therefore test for the condition where Clothing is "Jeans" and jeans are not permitted.

As you've probably realised, there are many ways of expressing the same thing with conditional expressions and the art is in writing it in a way that is clear enough that you will understand it when you read it through again later. It can help to write the condition out as a comment before you build it into an expression. Leaving the comment in place helps anyone reading it later.

The #ifdef and #ifndef, #else and #end constructs

The #ifdef and #ifndef statements can be used to determine whether or not an identifier has been defined and to conditionally execute statements. If the identifier passed to the #ifdef directive exists then the statements between the #ifdef statement and a corresponding #else or #end statement are evaluated. The #else directive is optional but, if specified and the identifier passed to the #ifdef directive does not exist, then directives between the #else and the corresponding #end statement are evaluated.

These statements can help to trap potential errors in a scene file or to set default values.

For example:

#ifndef(ObjectHeight) #declare ObjectHeight=1; #end

The #switch, #case, #range and #break Directives

The #switch directive provides another way of specifying conditional expressions but allows you to handle a succession of conditions as defined by a #case clause or a #range clause. This directive can seem a little daunting compared to using one or more #if directives, but there are some powerful ways of using it. Like the #if directive it uses float values rather than boolean, so two values whose difference is less than 1e-10 are considered equal.

This trivial example illustrates a number of basic points about the #switch directive.

  • If a #case or #range clause is not a match it skips to the next one, ignoring everything else in between.
  • If a #break is encountered within a matching #case or #range clause the #switch directive stops processing further lines, causing an immediate jump to the #end without processing subsequent sections, even if a subsequent condition would also have been satisfied.
  • When processing a matching #case or #range clause, if a #break is not encountered it continues through successive #case statements whether they match or not.

Although it doesn't contain a camera or any objects, the following example is a complete scene file. When you render it it'll generate a black image, but you'll be able to view the text that it writes out in the message stream.

#declare MyVariable = 5.2;
#debug concat("MyVariable: ",str(MyVariable,3,3),"\n")
#switch (MyVariable)
  #case (0)
    #debug "As close to nothing as makes a tiny difference.\n"
  #break 
  #case (1)
  #case (2)
  #case (3)
  #case (5)
  #case (7)
    #debug "A Prime number between 1 and 10 (actually within 1e-10 of a prime).\n"
  #break 
  #range (0,10)
    #debug "A number between 1 and 10 that is not within 1e-10 of a Prime number\n"
  #break
  #range (10,100)
    #debug "A number between 10 and 100 (actually 10+(1e-10) to 100+(1e-10)).\n"
  #break
  #else
    #debug "Everything else.\n"
    #debug "ie. Less than zero or greater than 100.\n"
#end

If the parameter to a #case clause matches the parameter to the #switch clause then everything up to the next #break, #else or #end clause is evaluated. Similarly, if the parameter to the #switch clause is between the minimum and maximum values of a #range clause then everything up to the next #break, #else or #end clause is evaluated.

If you are interested, you can explore the sensitivity to the accuracy of the floating point number comparison by setting a value using 'e' as follows:

#declare MyVariable = 5+(0.5e-10);
  or
#declare MyVariable = 5+(1.5e-10);

Using #switch with text values

The #switch, #case and #range clauses accept parameters that evaluate to float numbers. This includes the strcmp function which compares two strings and returns a zero if two strings match. By turning the #switch clause on its head a little, you can therefore use it with strings. Using the #switch directive with text can be particularly handy when writing macros that can generate an object in a large number of differently styles, because you can give each style a name and set parameters based on the name of the style specified.


Example of using #switch with text:

#declare MyVariable = "Ostrich";
#switch (0)
  #case (strcmp(strupr(MyVariable),"DOG"))
    #debug "Dog, DOG, or dog.\n"
    // Settings to be used for a dog
  #break 
  #case (strcmp(strupr(MyVariable),"CAT"))
    #debug "Cat, CAT, or cat.\n"
    // Settings to be used for a cat
  #break 
  #case (strcmp(strupr(MyVariable),"OSTRICH"))
    #debug "Ostrich, OSTRICH, or ostrich.\n"
    // Settings to be used for an ostrich
  #break 
  #else
    #debug "Not a dog, cat or ostrich.\n"
    // Stuff to do if an unexpected creature was encountered.
#end

Note that the strupr function is used to remove case sensitivity.

The following example illustrates the use of multiple #case clauses for a set of associated conditions to work out how many legs an animal has:

#declare Animal   = "Ostrich";
#switch (0)
  #case (strcmp(strupr(Animal),"DOG"))
  #case (strcmp(strupr(Animal),"CAT"))
  #case (strcmp(strupr(Animal),"RHINOCEROS"))
  #case (strcmp(strupr(Animal),"TABLE"))
    #declare LegCount = 4;
  #break 
  #case (strcmp(strupr(Animal),"OSTRICH"))
  #case (strcmp(strupr(Animal),"DUCK"))
    #declare LegCount = 2;
  #break 
  #case (strcmp(strupr(Animal),"WORM"))
  #case (strcmp(strupr(Animal),"DOLPHIN"))
    #declare LegCount = 0;
  #break 
  #else
    #declare LegCount = -1; // Unknown
#end

#debug concat("Animal: ",Animal,"\n") 
#if (LegCount<0) #debug "Number of Legs Unknown\n"
#else #debug concat("Number of Legs: ",str(LegCount,3,0),"\n")
#end

You can also test for abbreviations using the following construct:

  #case (strcmp(strupr(Animal),substr("RHINOCEROS",1,strlen(Animal))))

This would match 'Rhino' or 'Rhinoceros', but you clearly need to be a little careful with this as it would also match 'Rhinoc', 'R' and ''. To set a minimum abbreviation length use the max function:

  #case (strcmp(strupr(Animal),substr("RHINOCEROS",1,max(strlen(Animal),5))))

This would match 'Rhino', 'Rhinoceros' and everything in between, but not 'R', 'Rhin' or ''.

The #while, #end construct (Loops)

See also - HowTo:Use_macros_and_loops.

The #while, #end construct encapsulates a sequence of POV-Ray statements that are repeatedly executed for as long as the conditional expression specified is true. The conditional expression can be simple or complex and adheres to the same rules as apply to the #if statement described above.

As with the #if statement, the conditional expression must evaluate to a floating point number (often an integer value). This floating point number is interpreted as a boolean value, that is to say that if the expression evaluates to '0', it is considered 'false' and if it evaluates to anything else it is considered 'true'. If the condition is true then directives between the #while statement and a corresponding #end statement are evaluated. The conditional expression is then re-evaluated and, if still true the directives between the #while statement and the corresponding #end statement are repeated.

The statements repeat in a loop until the condition evaluates to false. It is up to you to control the loop by defining a suitable condition that will not loop indefinitely. If the condition never evaluates to false then the program will continue to evaluate the statements forever, until you hit the stop button, until the memory of your computer is exhausted or until you terminate the application (whichever happens first) see Wikipedia:Infinite_loop.

Note: extremely small values of about 1e-10 are considered zero (false) as a result of floating point number accuracy limits.

Here is an example of a simple loop:

#declare Count=0;
#while (Count < 5)
  object { MyObject translate x*3*Count }
  #declare Count=Count+1;
#end

This example places five copies of MyObject in a row spaced three units apart in the x-direction.

As described above (for the #if statement), you can combine conditional terms to build more complex looping conditional expressions using '&' (And), '|' (Or) and '!' (Not).