Difference between revisions of "HowTo:Use conditional structures"

From POV-Wiki
Jump to navigation Jump to search
 
(7 intermediate revisions by 2 users not shown)
Line 2: Line 2:
  
  
==#if==
+
==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 '''#if''' statement can only evaluate floating point expressions. There is no boolean logic using '''and''', '''or''' or '''not'''. The equivalent must be achieved using arithmetic.
+
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.
  
Here's an example of '''and''' where a rotation (for a texture, say) depends on both the row and column. Enclosing both the conditions in brackets isolates them and each produces either 0 or 1. Summing them will give 0, 1 or 2. The rotation will occur when both are true, producing 2.
+
For example:
 +
 
 +
<source lang=pov>
 +
#declare CameraDistance=25.8;
 
   
 
   
<source lang="pov">
+
... Definition of some complex object that looks like a sphere at great distances. ...
#local fRotateY = 0;
+
// If Row = 1 and Col = 4
+
#if (CameraDistance>100)
#if ((uiRow = 1) + (uiCol = 4) = 2)
+
  sphere { <0,0,0>, 1.2 pigment {rgb 1}}
    // Rotates only row 1, column 4.
+
#else
    #local fRotateY = 45;
+
  object {MyComplexObject}
 
#end
 
#end
 
</source>
 
</source>
  
And here's an example of '''or''' where the rotation depends on the column having either of two values. Again, enclosing both the conditions in brackets isolates them and each produces either a 0 or a 1. Summing them will give 0, 1 or 2. The rotation will occur when either or both are true and they produce 1 or 2. Note that the '''> 0''' is optional and just a matter of style.
+
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.
+
 
<source lang="pov">
+
===Conditional Conjunctions ===
#local fRotateY = 0;
+
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).
// If Row = 1 or Col = 6
+
 
#if ((uiRow = 1) + (uiCol = 6) > 0)
+
Here's an example that might take a little thinking about:
    // Rotates all of row 1 and all of column 6
+
<source lang=pov>
    #local fRotateY = 45;
+
#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
 
#end
 
</source>
 
</source>
  
And here's a truly ugly looking case which uses both. This is when you start wishing that they'd introduce a boolean type. ;-)
+
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.
  
<source lang="pov">
+
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.
#local fRotateY = 0;
 
// If (Row = 1 and Col = 4) or Col = 6
 
#if (((uiRow = 1) + (uiCol = 4) = 2) + (uiCol = 6) > 0)
 
    // Rotates row 1, column 4 and all of column 6
 
    #local fRotateY = 45;
 
#end
 
</source>
 
  
 +
== 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:
  
 +
<source lang="pov">
 +
#ifndef(ObjectHeight) #declare ObjectHeight=1; #end
 +
</source>
  
 
==The #switch, #case, #range and #break Directives==
 
==The #switch, #case, #range and #break Directives==
Line 53: Line 63:
 
<li>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.
 
<li>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.
 
</ul>
 
</ul>
 +
 +
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.
  
 
<source lang="pov">
 
<source lang="pov">
Line 80: Line 92:
 
</source>
 
</source>
  
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:
+
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:
  
 
<source lang="pov">
 
<source lang="pov">
Line 159: Line 173:
  
 
This would match 'Rhino', 'Rhinoceros' and everything in between, but not 'R', 'Rhin' or <nowiki>''</nowiki>.
 
This would match 'Rhino', 'Rhinoceros' and everything in between, but not 'R', 'Rhin' or <nowiki>''</nowiki>.
 +
 +
== 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:
 +
 +
<source lang=pov>
 +
#declare Count=0;
 +
#while (Count < 5)
 +
  object { MyObject translate x*3*Count }
 +
  #declare Count=Count+1;
 +
#end
 +
</source>
 +
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).

Latest revision as of 18:05, 31 July 2023

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