HowTo:Use conditional structures

From POV-Wiki
Revision as of 17:41, 10 June 2008 by Chrisb (talk | contribs) (Replaced incorrect #if assertions)
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 #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.
#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. To explore the sensitivity to the accuracy of the floating point number comparison you can set 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 ''.